diff --git a/.editorconfig b/.editorconfig index 4abe783c5b..77a6c4013d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -43,7 +43,7 @@ csharp_indent_labels = one_less_than_current ################### # Modifier preferences ################### -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion ################### # 'this.' qualification @@ -72,17 +72,17 @@ dotnet_style_predefined_type_for_member_access = true:suggestion # Constant fields should be PascalCase dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style -dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case # Static fields should have s_ prefix dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion -dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style -dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected dotnet_naming_style.static_prefix_style.required_prefix = s_ @@ -90,8 +90,8 @@ dotnet_naming_style.static_prefix_style.capitalization = camel_case # Internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion -dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields -dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style dotnet_naming_symbols.private_internal_fields.applicable_kinds = field dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal dotnet_naming_style.camel_case_underscore_style.required_prefix = _ @@ -201,483 +201,483 @@ dotnet_diagnostic.AvoidAsyncVoid.severity = suggestion ################### # Microsoft .NET Analyzers (CA) - Design Rules ################### -dotnet_diagnostic.CA1000.severity = none # Do not declare static members on generic types -dotnet_diagnostic.CA1001.severity = error # Types that own disposable fields should be disposable -dotnet_diagnostic.CA1016.severity = error # Mark assemblies with AssemblyVersionAttribute -dotnet_diagnostic.CA1027.severity = error # Mark enums with FlagsAttribute -dotnet_diagnostic.CA1030.severity = none # Use events where appropriate -dotnet_diagnostic.CA1031.severity = none # Do not catch general exception types -dotnet_diagnostic.CA1033.severity = none # Interface methods should be callable by child types -dotnet_diagnostic.CA1036.severity = none # Override methods on comparable types -dotnet_diagnostic.CA1056.severity = suggestion # URI properties should not be strings -dotnet_diagnostic.CA1060.severity = error # Move P/Invokes to NativeMethods class -dotnet_diagnostic.CA1061.severity = error # Do not hide base class methods -dotnet_diagnostic.CA1062.severity = error # Validate arguments of public methods -dotnet_diagnostic.CA1063.severity = error # Implement IDisposable correctly -dotnet_diagnostic.CA1064.severity = error # Exceptions should be public -dotnet_diagnostic.CA1065.severity = error # Do not raise exceptions in unexpected locations -dotnet_diagnostic.CA1066.severity = error # Implement IEquatable when overriding Equals -dotnet_diagnostic.CA1067.severity = error # Override Equals when implementing IEquatable -dotnet_diagnostic.CA1068.severity = error # CancellationToken parameters must come last -dotnet_diagnostic.CA1069.severity = error # Enums should not have duplicate values -dotnet_diagnostic.CA2000.severity = suggestion # Dispose objects before losing scope -dotnet_diagnostic.CA2002.severity = error # Do not lock on objects with weak identity -dotnet_diagnostic.CA2011.severity = error # Do not assign property within its setter -dotnet_diagnostic.CA2012.severity = error # Use ValueTasks correctly -dotnet_diagnostic.CA2013.severity = error # Do not use ReferenceEquals with value types -dotnet_diagnostic.CA2014.severity = error # Do not use stackalloc in loops -dotnet_diagnostic.CA2015.severity = error # Do not define finalizers for types derived from MemoryManager -dotnet_diagnostic.CA2016.severity = error # Forward the CancellationToken parameter to methods that take one -dotnet_diagnostic.CA2200.severity = error # Rethrow to preserve stack details -dotnet_diagnostic.CA2213.severity = error # Disposable fields should be disposed -dotnet_diagnostic.CA2214.severity = error # Do not call overridable methods in constructors -dotnet_diagnostic.CA2216.severity = error # Disposable types should declare finalizer -dotnet_diagnostic.CA2229.severity = error # Implement serialization constructors -dotnet_diagnostic.CA2231.severity = error # Overload operator equals on overriding ValueType.Equals -dotnet_diagnostic.CA2235.severity = error # Mark all non-serializable fields -dotnet_diagnostic.CA2237.severity = error # Mark ISerializable types with SerializableAttribute -dotnet_diagnostic.CA2241.severity = error # Provide correct arguments to formatting methods -dotnet_diagnostic.CA2242.severity = error # Test for NaN correctly +dotnet_diagnostic.CA1000.severity = none # Do not declare static members on generic types +dotnet_diagnostic.CA1001.severity = error # Types that own disposable fields should be disposable +dotnet_diagnostic.CA1016.severity = error # Mark assemblies with AssemblyVersionAttribute +dotnet_diagnostic.CA1027.severity = error # Mark enums with FlagsAttribute +dotnet_diagnostic.CA1030.severity = none # Use events where appropriate +dotnet_diagnostic.CA1031.severity = none # Do not catch general exception types +dotnet_diagnostic.CA1033.severity = none # Interface methods should be callable by child types +dotnet_diagnostic.CA1036.severity = none # Override methods on comparable types +dotnet_diagnostic.CA1056.severity = suggestion # URI properties should not be strings +dotnet_diagnostic.CA1060.severity = error # Move P/Invokes to NativeMethods class +dotnet_diagnostic.CA1061.severity = error # Do not hide base class methods +dotnet_diagnostic.CA1062.severity = error # Validate arguments of public methods +dotnet_diagnostic.CA1063.severity = error # Implement IDisposable correctly +dotnet_diagnostic.CA1064.severity = error # Exceptions should be public +dotnet_diagnostic.CA1065.severity = error # Do not raise exceptions in unexpected locations +dotnet_diagnostic.CA1066.severity = error # Implement IEquatable when overriding Equals +dotnet_diagnostic.CA1067.severity = error # Override Equals when implementing IEquatable +dotnet_diagnostic.CA1068.severity = error # CancellationToken parameters must come last +dotnet_diagnostic.CA1069.severity = error # Enums should not have duplicate values +dotnet_diagnostic.CA2000.severity = suggestion # Dispose objects before losing scope +dotnet_diagnostic.CA2002.severity = error # Do not lock on objects with weak identity +dotnet_diagnostic.CA2011.severity = error # Do not assign property within its setter +dotnet_diagnostic.CA2012.severity = error # Use ValueTasks correctly +dotnet_diagnostic.CA2013.severity = error # Do not use ReferenceEquals with value types +dotnet_diagnostic.CA2014.severity = error # Do not use stackalloc in loops +dotnet_diagnostic.CA2015.severity = error # Do not define finalizers for types derived from MemoryManager +dotnet_diagnostic.CA2016.severity = error # Forward the CancellationToken parameter to methods that take one +dotnet_diagnostic.CA2200.severity = error # Rethrow to preserve stack details +dotnet_diagnostic.CA2213.severity = error # Disposable fields should be disposed +dotnet_diagnostic.CA2214.severity = error # Do not call overridable methods in constructors +dotnet_diagnostic.CA2216.severity = error # Disposable types should declare finalizer +dotnet_diagnostic.CA2229.severity = error # Implement serialization constructors +dotnet_diagnostic.CA2231.severity = error # Overload operator equals on overriding ValueType.Equals +dotnet_diagnostic.CA2235.severity = error # Mark all non-serializable fields +dotnet_diagnostic.CA2237.severity = error # Mark ISerializable types with SerializableAttribute +dotnet_diagnostic.CA2241.severity = error # Provide correct arguments to formatting methods +dotnet_diagnostic.CA2242.severity = error # Test for NaN correctly ################### # Microsoft .NET Analyzers (CA) - Globalization Rules ################### -dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters -dotnet_diagnostic.CA1308.severity = none # Normalize strings to uppercase +dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters +dotnet_diagnostic.CA1308.severity = none # Normalize strings to uppercase ################### # Microsoft .NET Analyzers (CA) - Interoperability Rules ################### -dotnet_diagnostic.CA1401.severity = error # P/Invokes should not be visible +dotnet_diagnostic.CA1401.severity = error # P/Invokes should not be visible ################### # Microsoft .NET Analyzers (CA) - Maintainability Rules ################### -dotnet_diagnostic.CA1507.severity = error # Use nameof in place of string +dotnet_diagnostic.CA1507.severity = error # Use nameof in place of string ################### # Microsoft .NET Analyzers (CA) - Naming Rules ################### -dotnet_diagnostic.CA1710.severity = suggestion # Identifiers should have correct suffix -dotnet_diagnostic.CA1724.severity = none # Type Names Should Not Match Namespaces +dotnet_diagnostic.CA1710.severity = suggestion # Identifiers should have correct suffix +dotnet_diagnostic.CA1724.severity = none # Type Names Should Not Match Namespaces ################### # Microsoft .NET Analyzers (CA) - Performance Rules ################### -dotnet_diagnostic.CA1802.severity = error # Use Literals Where Appropriate -dotnet_diagnostic.CA1805.severity = error # Do not initialize unnecessarily -dotnet_diagnostic.CA1810.severity = none # Initialize reference type static fields inline -dotnet_diagnostic.CA1812.severity = error # Avoid uninstantiated internal classes -dotnet_diagnostic.CA1813.severity = error # Avoid unsealed attributes -dotnet_diagnostic.CA1814.severity = error # Prefer jagged arrays over multidimensional -dotnet_diagnostic.CA1815.severity = error # Override equals and operator equals on value types -dotnet_diagnostic.CA1821.severity = error # Remove empty finalizers -dotnet_diagnostic.CA1822.severity = error # Mark members as static -dotnet_diagnostic.CA1825.severity = error # Avoid zero-length array allocations -dotnet_diagnostic.CA1826.severity = error # Use property instead of Linq Enumerable method -dotnet_diagnostic.CA1827.severity = error # Do not use Count/LongCount when Any can be used -dotnet_diagnostic.CA1828.severity = error # Do not use CountAsync/LongCountAsync when AnyAsync can be used -dotnet_diagnostic.CA1829.severity = error # Use Length/Count property instead of Enumerable.Count method -dotnet_diagnostic.CA1830.severity = error # Prefer strongly-typed Append and Insert method overloads on StringBuilder -dotnet_diagnostic.CA1831.severity = error # Use AsSpan instead of Range-based indexers for string -dotnet_diagnostic.CA1832.severity = error # Use AsSpan or AsMemory instead of Range-based indexers for ReadOnlySpan/Memory -dotnet_diagnostic.CA1833.severity = error # Use AsSpan or AsMemory instead of Range-based indexers for Span/Memory -dotnet_diagnostic.CA1834.severity = error # Use StringBuilder.Append(char) for single character strings -dotnet_diagnostic.CA1835.severity = error # Prefer Memory-based overloads for ReadAsync and WriteAsync -dotnet_diagnostic.CA1836.severity = error # Prefer IsEmpty over Count when available -dotnet_diagnostic.CA1837.severity = error # Use Environment.ProcessId instead of Process.GetCurrentProcess().Id -dotnet_diagnostic.CA1838.severity = error # Avoid StringBuilder parameters for P/Invokes -dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task -dotnet_diagnostic.CA2008.severity = error # Do not create tasks without passing a TaskScheduler -dotnet_diagnostic.CA2009.severity = error # Do not call ToImmutableCollection on an ImmutableCollection value -dotnet_diagnostic.CA2207.severity = error # Initialize value type static fields inline +dotnet_diagnostic.CA1802.severity = error # Use Literals Where Appropriate +dotnet_diagnostic.CA1805.severity = error # Do not initialize unnecessarily +dotnet_diagnostic.CA1810.severity = none # Initialize reference type static fields inline +dotnet_diagnostic.CA1812.severity = error # Avoid uninstantiated internal classes +dotnet_diagnostic.CA1813.severity = error # Avoid unsealed attributes +dotnet_diagnostic.CA1814.severity = error # Prefer jagged arrays over multidimensional +dotnet_diagnostic.CA1815.severity = error # Override equals and operator equals on value types +dotnet_diagnostic.CA1821.severity = error # Remove empty finalizers +dotnet_diagnostic.CA1822.severity = error # Mark members as static +dotnet_diagnostic.CA1825.severity = error # Avoid zero-length array allocations +dotnet_diagnostic.CA1826.severity = error # Use property instead of Linq Enumerable method +dotnet_diagnostic.CA1827.severity = error # Do not use Count/LongCount when Any can be used +dotnet_diagnostic.CA1828.severity = error # Do not use CountAsync/LongCountAsync when AnyAsync can be used +dotnet_diagnostic.CA1829.severity = error # Use Length/Count property instead of Enumerable.Count method +dotnet_diagnostic.CA1830.severity = error # Prefer strongly-typed Append and Insert method overloads on StringBuilder +dotnet_diagnostic.CA1831.severity = error # Use AsSpan instead of Range-based indexers for string +dotnet_diagnostic.CA1832.severity = error # Use AsSpan or AsMemory instead of Range-based indexers for ReadOnlySpan/Memory +dotnet_diagnostic.CA1833.severity = error # Use AsSpan or AsMemory instead of Range-based indexers for Span/Memory +dotnet_diagnostic.CA1834.severity = error # Use StringBuilder.Append(char) for single character strings +dotnet_diagnostic.CA1835.severity = error # Prefer Memory-based overloads for ReadAsync and WriteAsync +dotnet_diagnostic.CA1836.severity = error # Prefer IsEmpty over Count when available +dotnet_diagnostic.CA1837.severity = error # Use Environment.ProcessId instead of Process.GetCurrentProcess().Id +dotnet_diagnostic.CA1838.severity = error # Avoid StringBuilder parameters for P/Invokes +dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task +dotnet_diagnostic.CA2008.severity = error # Do not create tasks without passing a TaskScheduler +dotnet_diagnostic.CA2009.severity = error # Do not call ToImmutableCollection on an ImmutableCollection value +dotnet_diagnostic.CA2207.severity = error # Initialize value type static fields inline ################### # Microsoft .NET Analyzers (CA) - Security Rules ################### # SQL Injection & Command Injection -dotnet_diagnostic.CA2100.severity = error # Review SQL queries for security vulnerabilities -dotnet_diagnostic.CA3001.severity = error # Review code for SQL injection vulnerabilities -dotnet_diagnostic.CA3006.severity = error # Review code for process command injection vulnerabilities +dotnet_diagnostic.CA2100.severity = error # Review SQL queries for security vulnerabilities +dotnet_diagnostic.CA3001.severity = error # Review code for SQL injection vulnerabilities +dotnet_diagnostic.CA3006.severity = error # Review code for process command injection vulnerabilities # Cross-Site Scripting (XSS) & Injection Attacks -dotnet_diagnostic.CA3002.severity = error # Review code for XSS vulnerabilities -dotnet_diagnostic.CA3003.severity = error # Review code for file path injection vulnerabilities -dotnet_diagnostic.CA3005.severity = error # Review code for LDAP injection vulnerabilities -dotnet_diagnostic.CA3007.severity = error # Review code for open redirect vulnerabilities -dotnet_diagnostic.CA3008.severity = error # Review code for XPath injection vulnerabilities -dotnet_diagnostic.CA3009.severity = error # Review code for XML injection vulnerabilities -dotnet_diagnostic.CA3010.severity = error # Review code for XAML injection vulnerabilities -dotnet_diagnostic.CA3011.severity = error # Review code for DLL injection vulnerabilities -dotnet_diagnostic.CA3012.severity = error # Review code for regex injection vulnerabilities -dotnet_diagnostic.CA3004.severity = error # Review code for information disclosure vulnerabilities +dotnet_diagnostic.CA3002.severity = error # Review code for XSS vulnerabilities +dotnet_diagnostic.CA3003.severity = error # Review code for file path injection vulnerabilities +dotnet_diagnostic.CA3005.severity = error # Review code for LDAP injection vulnerabilities +dotnet_diagnostic.CA3007.severity = error # Review code for open redirect vulnerabilities +dotnet_diagnostic.CA3008.severity = error # Review code for XPath injection vulnerabilities +dotnet_diagnostic.CA3009.severity = error # Review code for XML injection vulnerabilities +dotnet_diagnostic.CA3010.severity = error # Review code for XAML injection vulnerabilities +dotnet_diagnostic.CA3011.severity = error # Review code for DLL injection vulnerabilities +dotnet_diagnostic.CA3012.severity = error # Review code for regex injection vulnerabilities +dotnet_diagnostic.CA3004.severity = error # Review code for information disclosure vulnerabilities # Insecure Deserialization -dotnet_diagnostic.CA2300.severity = error # Do not use insecure deserializer BinaryFormatter -dotnet_diagnostic.CA2301.severity = error # Do not call BinaryFormatter.Deserialize without setting Binder -dotnet_diagnostic.CA2302.severity = error # Ensure BinaryFormatter.Binder is set before deserializing -dotnet_diagnostic.CA2305.severity = error # Do not use insecure deserializer LosFormatter -dotnet_diagnostic.CA2310.severity = error # Do not use insecure deserializer NetDataContractSerializer -dotnet_diagnostic.CA2311.severity = error # Do not deserialize without setting NetDataContractSerializer.Binder -dotnet_diagnostic.CA2312.severity = error # Ensure NetDataContractSerializer.Binder is set before deserializing -dotnet_diagnostic.CA2315.severity = error # Do not use insecure deserializer ObjectStateFormatter -dotnet_diagnostic.CA2321.severity = error # Do not deserialize with JavaScriptSerializer using SimpleTypeResolver -dotnet_diagnostic.CA2322.severity = error # Ensure JavaScriptSerializer not initialized with SimpleTypeResolver -dotnet_diagnostic.CA2326.severity = error # Do not use TypeNameHandling values other than None -dotnet_diagnostic.CA2327.severity = error # Do not use insecure JsonSerializerSettings -dotnet_diagnostic.CA2328.severity = error # Ensure that JsonSerializerSettings are secure -dotnet_diagnostic.CA2329.severity = error # Do not deserialize with JsonSerializer using insecure configuration -dotnet_diagnostic.CA2330.severity = error # Ensure JsonSerializer has secure configuration when deserializing -dotnet_diagnostic.CA2350.severity = error # Ensure DataTable.ReadXml()'s input is trusted -dotnet_diagnostic.CA2351.severity = error # Ensure DataSet.ReadXml()'s input is trusted -dotnet_diagnostic.CA2352.severity = error # Unsafe DataSet/DataTable in serializable type vulnerable to RCE -dotnet_diagnostic.CA2353.severity = error # Unsafe DataSet or DataTable in serializable type -dotnet_diagnostic.CA2354.severity = error # Unsafe DataSet/DataTable in deserialized object graph vulnerable to RCE -dotnet_diagnostic.CA2355.severity = error # Unsafe DataSet or DataTable in deserialized object graph -dotnet_diagnostic.CA2356.severity = error # Unsafe DataSet/DataTable in web deserialized object graph -dotnet_diagnostic.CA2361.severity = error # Ensure autogenerated class with DataSet.ReadXml() not used with untrusted data -dotnet_diagnostic.CA2362.severity = error # Unsafe DataSet/DataTable in autogenerated serializable type vulnerable to RCE -dotnet_diagnostic.CA5360.severity = error # Do not call dangerous methods in deserialization -dotnet_diagnostic.CA5362.severity = error # Potential reference cycle in deserialized object graph +dotnet_diagnostic.CA2300.severity = error # Do not use insecure deserializer BinaryFormatter +dotnet_diagnostic.CA2301.severity = error # Do not call BinaryFormatter.Deserialize without setting Binder +dotnet_diagnostic.CA2302.severity = error # Ensure BinaryFormatter.Binder is set before deserializing +dotnet_diagnostic.CA2305.severity = error # Do not use insecure deserializer LosFormatter +dotnet_diagnostic.CA2310.severity = error # Do not use insecure deserializer NetDataContractSerializer +dotnet_diagnostic.CA2311.severity = error # Do not deserialize without setting NetDataContractSerializer.Binder +dotnet_diagnostic.CA2312.severity = error # Ensure NetDataContractSerializer.Binder is set before deserializing +dotnet_diagnostic.CA2315.severity = error # Do not use insecure deserializer ObjectStateFormatter +dotnet_diagnostic.CA2321.severity = error # Do not deserialize with JavaScriptSerializer using SimpleTypeResolver +dotnet_diagnostic.CA2322.severity = error # Ensure JavaScriptSerializer not initialized with SimpleTypeResolver +dotnet_diagnostic.CA2326.severity = error # Do not use TypeNameHandling values other than None +dotnet_diagnostic.CA2327.severity = error # Do not use insecure JsonSerializerSettings +dotnet_diagnostic.CA2328.severity = error # Ensure that JsonSerializerSettings are secure +dotnet_diagnostic.CA2329.severity = error # Do not deserialize with JsonSerializer using insecure configuration +dotnet_diagnostic.CA2330.severity = error # Ensure JsonSerializer has secure configuration when deserializing +dotnet_diagnostic.CA2350.severity = error # Ensure DataTable.ReadXml()'s input is trusted +dotnet_diagnostic.CA2351.severity = error # Ensure DataSet.ReadXml()'s input is trusted +dotnet_diagnostic.CA2352.severity = error # Unsafe DataSet/DataTable in serializable type vulnerable to RCE +dotnet_diagnostic.CA2353.severity = error # Unsafe DataSet or DataTable in serializable type +dotnet_diagnostic.CA2354.severity = error # Unsafe DataSet/DataTable in deserialized object graph vulnerable to RCE +dotnet_diagnostic.CA2355.severity = error # Unsafe DataSet or DataTable in deserialized object graph +dotnet_diagnostic.CA2356.severity = error # Unsafe DataSet/DataTable in web deserialized object graph +dotnet_diagnostic.CA2361.severity = error # Ensure autogenerated class with DataSet.ReadXml() not used with untrusted data +dotnet_diagnostic.CA2362.severity = error # Unsafe DataSet/DataTable in autogenerated serializable type vulnerable to RCE +dotnet_diagnostic.CA5360.severity = error # Do not call dangerous methods in deserialization +dotnet_diagnostic.CA5362.severity = error # Potential reference cycle in deserialized object graph # Cryptography - Weak & Broken Algorithms -dotnet_diagnostic.CA5350.severity = error # Do not use weak cryptographic algorithms (SHA1, RIPEMD160, TripleDES) -dotnet_diagnostic.CA5351.severity = error # Do not use broken cryptographic algorithms (MD5, DES, RC2) -dotnet_diagnostic.CA5358.severity = error # Do not use unsafe cipher modes (ECB, OFB, CFB) -dotnet_diagnostic.CA5384.severity = error # Do not use Digital Signature Algorithm (DSA) -dotnet_diagnostic.CA5385.severity = error # Use RSA algorithm with sufficient key size (>= 2048 bits) -dotnet_diagnostic.CA5390.severity = error # Do not hard-code encryption key -dotnet_diagnostic.CA5394.severity = error # Do not use insecure randomness (use RNGCryptoServiceProvider) -dotnet_diagnostic.CA5401.severity = error # Do not use CreateEncryptor with non-default IV -dotnet_diagnostic.CA5403.severity = error # Do not hard-code certificate -dotnet_diagnostic.CA5373.severity = error # Do not use obsolete key derivation function +dotnet_diagnostic.CA5350.severity = error # Do not use weak cryptographic algorithms (SHA1, RIPEMD160, TripleDES) +dotnet_diagnostic.CA5351.severity = error # Do not use broken cryptographic algorithms (MD5, DES, RC2) +dotnet_diagnostic.CA5358.severity = error # Do not use unsafe cipher modes (ECB, OFB, CFB) +dotnet_diagnostic.CA5384.severity = error # Do not use Digital Signature Algorithm (DSA) +dotnet_diagnostic.CA5385.severity = error # Use RSA algorithm with sufficient key size (>= 2048 bits) +dotnet_diagnostic.CA5390.severity = error # Do not hard-code encryption key +dotnet_diagnostic.CA5394.severity = error # Do not use insecure randomness (use RNGCryptoServiceProvider) +dotnet_diagnostic.CA5401.severity = error # Do not use CreateEncryptor with non-default IV +dotnet_diagnostic.CA5403.severity = error # Do not hard-code certificate +dotnet_diagnostic.CA5373.severity = error # Do not use obsolete key derivation function # TLS/SSL Protocol Security -dotnet_diagnostic.CA5359.severity = error # Do not disable certificate validation -dotnet_diagnostic.CA5361.severity = error # Do not disable SChannel use of strong crypto -dotnet_diagnostic.CA5364.severity = error # Do not use deprecated security protocols (TLS 1.0, TLS 1.1, SSL3) -dotnet_diagnostic.CA5378.severity = error # Do not disable ServicePointManagerSecurityProtocols -dotnet_diagnostic.CA5386.severity = error # Avoid hardcoding SecurityProtocolType value -dotnet_diagnostic.CA5397.severity = error # Do not use deprecated SslProtocols values -dotnet_diagnostic.CA5398.severity = error # Avoid hardcoded SslProtocols values -dotnet_diagnostic.CA5399.severity = error # Definitely disable HttpClient certificate revocation list check -dotnet_diagnostic.CA5380.severity = error # Do not add certificates to root store -dotnet_diagnostic.CA5381.severity = error # Ensure certificates are not added to root store +dotnet_diagnostic.CA5359.severity = error # Do not disable certificate validation +dotnet_diagnostic.CA5361.severity = error # Do not disable SChannel use of strong crypto +dotnet_diagnostic.CA5364.severity = error # Do not use deprecated security protocols (TLS 1.0, TLS 1.1, SSL3) +dotnet_diagnostic.CA5378.severity = error # Do not disable ServicePointManagerSecurityProtocols +dotnet_diagnostic.CA5386.severity = error # Avoid hardcoding SecurityProtocolType value +dotnet_diagnostic.CA5397.severity = error # Do not use deprecated SslProtocols values +dotnet_diagnostic.CA5398.severity = error # Avoid hardcoded SslProtocols values +dotnet_diagnostic.CA5399.severity = error # Definitely disable HttpClient certificate revocation list check +dotnet_diagnostic.CA5380.severity = error # Do not add certificates to root store +dotnet_diagnostic.CA5381.severity = error # Ensure certificates are not added to root store # XML Security -dotnet_diagnostic.CA3061.severity = error # Do not add schema by URL -dotnet_diagnostic.CA3075.severity = error # Insecure DTD processing -dotnet_diagnostic.CA3076.severity = error # Insecure XSLT script execution -dotnet_diagnostic.CA3077.severity = error # Insecure processing in API design, XML Document and XML Text Reader -dotnet_diagnostic.CA5366.severity = error # Use XmlReader for DataSet read XML -dotnet_diagnostic.CA5369.severity = error # Use XmlReader for deserialize -dotnet_diagnostic.CA5370.severity = error # Use XmlReader for validating reader -dotnet_diagnostic.CA5371.severity = error # Use XmlReader for schema read -dotnet_diagnostic.CA5372.severity = error # Use XmlReader for XPathDocument -dotnet_diagnostic.CA5374.severity = error # Do not use XslTransform +dotnet_diagnostic.CA3061.severity = error # Do not add schema by URL +dotnet_diagnostic.CA3075.severity = error # Insecure DTD processing +dotnet_diagnostic.CA3076.severity = error # Insecure XSLT script execution +dotnet_diagnostic.CA3077.severity = error # Insecure processing in API design, XML Document and XML Text Reader +dotnet_diagnostic.CA5366.severity = error # Use XmlReader for DataSet read XML +dotnet_diagnostic.CA5369.severity = error # Use XmlReader for deserialize +dotnet_diagnostic.CA5370.severity = error # Use XmlReader for validating reader +dotnet_diagnostic.CA5371.severity = error # Use XmlReader for schema read +dotnet_diagnostic.CA5372.severity = error # Use XmlReader for XPathDocument +dotnet_diagnostic.CA5374.severity = error # Do not use XslTransform # Web Security -dotnet_diagnostic.CA3147.severity = error # Mark verb handlers with ValidateAntiForgeryToken -dotnet_diagnostic.CA5363.severity = error # Do not disable request validation -dotnet_diagnostic.CA5365.severity = error # Do not disable HTTP header checking -dotnet_diagnostic.CA5368.severity = error # Set ViewStateUserKey for classes derived from Page +dotnet_diagnostic.CA3147.severity = error # Mark verb handlers with ValidateAntiForgeryToken +dotnet_diagnostic.CA5363.severity = error # Do not disable request validation +dotnet_diagnostic.CA5365.severity = error # Do not disable HTTP header checking +dotnet_diagnostic.CA5368.severity = error # Set ViewStateUserKey for classes derived from Page # P/Invoke & DLL Security -dotnet_diagnostic.CA2101.severity = error # Specify marshalling for P/Invoke string arguments -dotnet_diagnostic.CA5393.severity = error # Do not use unsafe DllImportSearchPath value +dotnet_diagnostic.CA2101.severity = error # Specify marshalling for P/Invoke string arguments +dotnet_diagnostic.CA5393.severity = error # Do not use unsafe DllImportSearchPath value # Archive & File Security -dotnet_diagnostic.CA5389.severity = error # Do not add archive item's path to target file system path (Zip Slip) +dotnet_diagnostic.CA5389.severity = error # Do not add archive item's path to target file system path (Zip Slip) # Token Validation & Authentication -dotnet_diagnostic.CA5404.severity = error # Do not disable token validation checks -dotnet_diagnostic.CA5405.severity = error # Do not always skip token validation in delegates +dotnet_diagnostic.CA5404.severity = error # Do not disable token validation checks +dotnet_diagnostic.CA5405.severity = error # Do not always skip token validation in delegates # Other Security Rules -dotnet_diagnostic.CA2109.severity = error # Review visible event handlers -dotnet_diagnostic.CA2119.severity = error # Seal methods that satisfy private interfaces -dotnet_diagnostic.CA2153.severity = error # Do not catch corrupted state exceptions -dotnet_diagnostic.CA5367.severity = error # Do not serialize types with pointer fields +dotnet_diagnostic.CA2109.severity = error # Review visible event handlers +dotnet_diagnostic.CA2119.severity = error # Seal methods that satisfy private interfaces +dotnet_diagnostic.CA2153.severity = error # Do not catch corrupted state exceptions +dotnet_diagnostic.CA5367.severity = error # Do not serialize types with pointer fields ################### # Microsoft .NET Analyzers (CA) - Reliability Rules (Additional) ################### -dotnet_diagnostic.CA2017.severity = error # Parameter count mismatch in logging -dotnet_diagnostic.CA2018.severity = error # Buffer.BlockCopy count argument -dotnet_diagnostic.CA2019.severity = error # ThreadStatic fields should not use inline initialization -dotnet_diagnostic.CA2020.severity = error # Prevent behavioral change with IntPtr/UIntPtr -dotnet_diagnostic.CA2021.severity = error # Don't call Cast/OfType with incompatible types -dotnet_diagnostic.CA2022.severity = error # Avoid inexact read with Stream.Read -dotnet_diagnostic.CA2023.severity = error # Invalid braces in message template -dotnet_diagnostic.CA2025.severity = error # Do not pass IDisposable into unawaited tasks +dotnet_diagnostic.CA2017.severity = error # Parameter count mismatch in logging +dotnet_diagnostic.CA2018.severity = error # Buffer.BlockCopy count argument +dotnet_diagnostic.CA2019.severity = error # ThreadStatic fields should not use inline initialization +dotnet_diagnostic.CA2020.severity = error # Prevent behavioral change with IntPtr/UIntPtr +dotnet_diagnostic.CA2021.severity = error # Don't call Cast/OfType with incompatible types +dotnet_diagnostic.CA2022.severity = error # Avoid inexact read with Stream.Read +dotnet_diagnostic.CA2023.severity = error # Invalid braces in message template +dotnet_diagnostic.CA2025.severity = error # Do not pass IDisposable into unawaited tasks ################### # Roslynator Analyzers (RCS) - Code Simplification ################### -dotnet_diagnostic.RCS1001.severity = error # Add braces (when expression spans over multiple lines) -dotnet_diagnostic.RCS1005.severity = error # Simplify nested using statement -dotnet_diagnostic.RCS1006.severity = error # Merge 'else' with nested 'if' -dotnet_diagnostic.RCS1020.severity = error # Simplify Nullable to T? -dotnet_diagnostic.RCS1049.severity = error # Simplify boolean comparison -dotnet_diagnostic.RCS1068.severity = error # Simplify logical negation -dotnet_diagnostic.RCS1069.severity = error # Remove unnecessary case label -dotnet_diagnostic.RCS1071.severity = error # Remove redundant base constructor call -dotnet_diagnostic.RCS1073.severity = error # Convert 'if' to 'return' statement -dotnet_diagnostic.RCS1074.severity = error # Remove redundant constructor -dotnet_diagnostic.RCS1084.severity = error # Use coalesce expression instead of conditional expression -dotnet_diagnostic.RCS1128.severity = error # Use coalesce expression -dotnet_diagnostic.RCS1143.severity = error # Simplify coalesce expression -dotnet_diagnostic.RCS1171.severity = error # Simplify lazy initialization -dotnet_diagnostic.RCS1173.severity = error # Use coalesce expression instead of 'if' -dotnet_diagnostic.RCS1259.severity = error # Remove empty syntax (replaces RCS1066) -dotnet_diagnostic.RCS1264.severity = error # Use 'var' or explicit type (replaces RCS1010, RCS1176, RCS1177) +dotnet_diagnostic.RCS1001.severity = error # Add braces (when expression spans over multiple lines) +dotnet_diagnostic.RCS1005.severity = error # Simplify nested using statement +dotnet_diagnostic.RCS1006.severity = error # Merge 'else' with nested 'if' +dotnet_diagnostic.RCS1020.severity = error # Simplify Nullable to T? +dotnet_diagnostic.RCS1049.severity = error # Simplify boolean comparison +dotnet_diagnostic.RCS1068.severity = error # Simplify logical negation +dotnet_diagnostic.RCS1069.severity = error # Remove unnecessary case label +dotnet_diagnostic.RCS1071.severity = error # Remove redundant base constructor call +dotnet_diagnostic.RCS1073.severity = error # Convert 'if' to 'return' statement +dotnet_diagnostic.RCS1074.severity = error # Remove redundant constructor +dotnet_diagnostic.RCS1084.severity = error # Use coalesce expression instead of conditional expression +dotnet_diagnostic.RCS1128.severity = error # Use coalesce expression +dotnet_diagnostic.RCS1143.severity = error # Simplify coalesce expression +dotnet_diagnostic.RCS1171.severity = error # Simplify lazy initialization +dotnet_diagnostic.RCS1173.severity = error # Use coalesce expression instead of 'if' +dotnet_diagnostic.RCS1259.severity = error # Remove empty syntax (replaces RCS1066) +dotnet_diagnostic.RCS1264.severity = error # Use 'var' or explicit type (replaces RCS1010, RCS1176, RCS1177) ################### # Roslynator Analyzers (RCS) - Code Quality & Best Practices ################### -dotnet_diagnostic.RCS1018.severity = error # Add/remove accessibility modifiers -dotnet_diagnostic.RCS1037.severity = error # Remove trailing white-space -dotnet_diagnostic.RCS1055.severity = error # Unnecessary semicolon at the end of declaration -dotnet_diagnostic.RCS1078.severity = error # Use "" or 'string.Empty' -dotnet_diagnostic.RCS1085.severity = error # Use auto-implemented property -dotnet_diagnostic.RCS1090.severity = error # Add/remove 'ConfigureAwait(false)' call -dotnet_diagnostic.RCS1102.severity = error # Make class static -dotnet_diagnostic.RCS1105.severity = error # Unnecessary interpolation -dotnet_diagnostic.RCS1138.severity = error # Add summary to documentation comment -dotnet_diagnostic.RCS1139.severity = error # Add summary element to documentation comment -dotnet_diagnostic.RCS1158.severity = none # Static member in generic type should use a type parameter -dotnet_diagnostic.RCS1163.severity = none # Unused parameter -dotnet_diagnostic.RCS1166.severity = error # Value type object is never equal to null -dotnet_diagnostic.RCS1168.severity = suggestion # Parameter name differs from base name -dotnet_diagnostic.RCS1179.severity = error # Unnecessary assignment -dotnet_diagnostic.RCS1180.severity = error # Inline lazy initialization -dotnet_diagnostic.RCS1188.severity = error # Remove redundant auto-property initialization -dotnet_diagnostic.RCS1201.severity = error # Use method chaining -dotnet_diagnostic.RCS1207.severity = error # Use anonymous function or method group -dotnet_diagnostic.RCS1211.severity = error # Remove unnecessary 'else' -dotnet_diagnostic.RCS1231.severity = suggestion # Make parameter ref read-only -dotnet_diagnostic.RCS1242.severity = error # Do not pass non-read-only struct by read-only reference -dotnet_diagnostic.RCS1248.severity = error # Normalize null check -dotnet_diagnostic.RCS1256.severity = none # Invalid argument null check +dotnet_diagnostic.RCS1018.severity = error # Add/remove accessibility modifiers +dotnet_diagnostic.RCS1037.severity = error # Remove trailing white-space +dotnet_diagnostic.RCS1055.severity = error # Unnecessary semicolon at the end of declaration +dotnet_diagnostic.RCS1078.severity = error # Use "" or 'string.Empty' +dotnet_diagnostic.RCS1085.severity = error # Use auto-implemented property +dotnet_diagnostic.RCS1090.severity = error # Add/remove 'ConfigureAwait(false)' call +dotnet_diagnostic.RCS1102.severity = error # Make class static +dotnet_diagnostic.RCS1105.severity = error # Unnecessary interpolation +dotnet_diagnostic.RCS1138.severity = error # Add summary to documentation comment +dotnet_diagnostic.RCS1139.severity = error # Add summary element to documentation comment +dotnet_diagnostic.RCS1158.severity = none # Static member in generic type should use a type parameter +dotnet_diagnostic.RCS1163.severity = none # Unused parameter +dotnet_diagnostic.RCS1166.severity = error # Value type object is never equal to null +dotnet_diagnostic.RCS1168.severity = suggestion # Parameter name differs from base name +dotnet_diagnostic.RCS1179.severity = error # Unnecessary assignment +dotnet_diagnostic.RCS1180.severity = error # Inline lazy initialization +dotnet_diagnostic.RCS1188.severity = error # Remove redundant auto-property initialization +dotnet_diagnostic.RCS1201.severity = error # Use method chaining +dotnet_diagnostic.RCS1207.severity = error # Use anonymous function or method group +dotnet_diagnostic.RCS1211.severity = error # Remove unnecessary 'else' +dotnet_diagnostic.RCS1231.severity = suggestion # Make parameter ref read-only +dotnet_diagnostic.RCS1242.severity = error # Do not pass non-read-only struct by read-only reference +dotnet_diagnostic.RCS1248.severity = error # Normalize null check +dotnet_diagnostic.RCS1256.severity = none # Invalid argument null check ################### # Roslynator Analyzers (RCS) - Performance & Optimization ################### -dotnet_diagnostic.RCS1058.severity = error # Use compound assignment -dotnet_diagnostic.RCS1077.severity = error # Optimize LINQ method call -dotnet_diagnostic.RCS1080.severity = error # Use 'Count/Length' property instead of 'Any' method -dotnet_diagnostic.RCS1112.severity = error # Combine 'Enumerable.Where' method chain -dotnet_diagnostic.RCS1190.severity = error # Join string expressions -dotnet_diagnostic.RCS1195.severity = error # Use ^ operator -dotnet_diagnostic.RCS1197.severity = error # Optimize StringBuilder.Append/AppendLine call -dotnet_diagnostic.RCS1198.severity = none # Avoid unnecessary boxing of value type -dotnet_diagnostic.RCS1214.severity = error # Unnecessary interpolated string -dotnet_diagnostic.RCS1235.severity = error # Optimize method call +dotnet_diagnostic.RCS1058.severity = error # Use compound assignment +dotnet_diagnostic.RCS1077.severity = error # Optimize LINQ method call +dotnet_diagnostic.RCS1080.severity = error # Use 'Count/Length' property instead of 'Any' method +dotnet_diagnostic.RCS1112.severity = error # Combine 'Enumerable.Where' method chain +dotnet_diagnostic.RCS1190.severity = error # Join string expressions +dotnet_diagnostic.RCS1195.severity = error # Use ^ operator +dotnet_diagnostic.RCS1197.severity = error # Optimize StringBuilder.Append/AppendLine call +dotnet_diagnostic.RCS1198.severity = none # Avoid unnecessary boxing of value type +dotnet_diagnostic.RCS1214.severity = error # Unnecessary interpolated string +dotnet_diagnostic.RCS1235.severity = error # Optimize method call ################### # StyleCop Analyzers (SA) - Spacing Rules ################### -dotnet_diagnostic.SA1000.severity = error # Keywords must be spaced correctly -dotnet_diagnostic.SA1001.severity = error # Commas must be spaced correctly -dotnet_diagnostic.SA1002.severity = error # Semicolons must be spaced correctly -dotnet_diagnostic.SA1003.severity = error # Symbols must be spaced correctly -dotnet_diagnostic.SA1004.severity = error # Documentation lines must begin with single space -dotnet_diagnostic.SA1005.severity = error # Single line comments must begin with single space -dotnet_diagnostic.SA1006.severity = error # Preprocessor keywords must not be preceded by space -dotnet_diagnostic.SA1007.severity = error # Operator keyword must be followed by space -dotnet_diagnostic.SA1008.severity = error # Opening parenthesis must be spaced correctly -dotnet_diagnostic.SA1009.severity = error # Closing parenthesis must be spaced correctly -dotnet_diagnostic.SA1010.severity = none # Opening square brackets must be spaced correctly -dotnet_diagnostic.SA1011.severity = error # Closing square brackets must be spaced correctly -dotnet_diagnostic.SA1012.severity = error # Opening braces must be spaced correctly -dotnet_diagnostic.SA1013.severity = error # Closing braces must be spaced correctly -dotnet_diagnostic.SA1014.severity = error # Opening generic brackets must be spaced correctly -dotnet_diagnostic.SA1015.severity = error # Closing generic brackets must be spaced correctly -dotnet_diagnostic.SA1016.severity = error # Opening attribute brackets must be spaced correctly -dotnet_diagnostic.SA1017.severity = error # Closing attribute brackets must be spaced correctly -dotnet_diagnostic.SA1018.severity = error # Nullable type symbols must not be preceded by space -dotnet_diagnostic.SA1019.severity = error # Member access symbols must be spaced correctly -dotnet_diagnostic.SA1020.severity = error # Increment decrement symbols must be spaced correctly -dotnet_diagnostic.SA1021.severity = error # Negative signs must be spaced correctly -dotnet_diagnostic.SA1022.severity = error # Positive signs must be spaced correctly -dotnet_diagnostic.SA1023.severity = error # Dereference and access of symbols must be spaced correctly -dotnet_diagnostic.SA1024.severity = error # Colons must be spaced correctly -dotnet_diagnostic.SA1025.severity = error # Code must not contain multiple whitespace in a row -dotnet_diagnostic.SA1026.severity = error # Code must not contain space after new keyword in implicitly typed array allocation -dotnet_diagnostic.SA1027.severity = error # Use tabs correctly -dotnet_diagnostic.SA1028.severity = error # Code must not contain trailing whitespace +dotnet_diagnostic.SA1000.severity = error # Keywords must be spaced correctly +dotnet_diagnostic.SA1001.severity = error # Commas must be spaced correctly +dotnet_diagnostic.SA1002.severity = error # Semicolons must be spaced correctly +dotnet_diagnostic.SA1003.severity = error # Symbols must be spaced correctly +dotnet_diagnostic.SA1004.severity = error # Documentation lines must begin with single space +dotnet_diagnostic.SA1005.severity = error # Single line comments must begin with single space +dotnet_diagnostic.SA1006.severity = error # Preprocessor keywords must not be preceded by space +dotnet_diagnostic.SA1007.severity = error # Operator keyword must be followed by space +dotnet_diagnostic.SA1008.severity = error # Opening parenthesis must be spaced correctly +dotnet_diagnostic.SA1009.severity = error # Closing parenthesis must be spaced correctly +dotnet_diagnostic.SA1010.severity = none # Opening square brackets must be spaced correctly +dotnet_diagnostic.SA1011.severity = error # Closing square brackets must be spaced correctly +dotnet_diagnostic.SA1012.severity = error # Opening braces must be spaced correctly +dotnet_diagnostic.SA1013.severity = error # Closing braces must be spaced correctly +dotnet_diagnostic.SA1014.severity = error # Opening generic brackets must be spaced correctly +dotnet_diagnostic.SA1015.severity = error # Closing generic brackets must be spaced correctly +dotnet_diagnostic.SA1016.severity = error # Opening attribute brackets must be spaced correctly +dotnet_diagnostic.SA1017.severity = error # Closing attribute brackets must be spaced correctly +dotnet_diagnostic.SA1018.severity = error # Nullable type symbols must not be preceded by space +dotnet_diagnostic.SA1019.severity = error # Member access symbols must be spaced correctly +dotnet_diagnostic.SA1020.severity = error # Increment decrement symbols must be spaced correctly +dotnet_diagnostic.SA1021.severity = error # Negative signs must be spaced correctly +dotnet_diagnostic.SA1022.severity = error # Positive signs must be spaced correctly +dotnet_diagnostic.SA1023.severity = error # Dereference and access of symbols must be spaced correctly +dotnet_diagnostic.SA1024.severity = error # Colons must be spaced correctly +dotnet_diagnostic.SA1025.severity = error # Code must not contain multiple whitespace in a row +dotnet_diagnostic.SA1026.severity = error # Code must not contain space after new keyword in implicitly typed array allocation +dotnet_diagnostic.SA1027.severity = error # Use tabs correctly +dotnet_diagnostic.SA1028.severity = error # Code must not contain trailing whitespace ################### # StyleCop Analyzers (SA) - Readability Rules ################### -dotnet_diagnostic.SA1100.severity = error # Do not prefix calls with base unless local implementation exists -dotnet_diagnostic.SA1101.severity = none # Prefix local calls with this -dotnet_diagnostic.SA1102.severity = error # Query clause must follow previous clause -dotnet_diagnostic.SA1103.severity = error # Query clauses must be on same line or separate lines -dotnet_diagnostic.SA1104.severity = error # Query clause must begin on new line when previous clause spans multiple lines -dotnet_diagnostic.SA1105.severity = error # Query clauses spanning multiple lines must begin on own line -dotnet_diagnostic.SA1106.severity = error # Code must not contain empty statements -dotnet_diagnostic.SA1107.severity = error # Code must not contain multiple statements on one line -dotnet_diagnostic.SA1108.severity = error # Block statements must not contain embedded comments -dotnet_diagnostic.SA1110.severity = error # Opening parenthesis or bracket must be on declaration line -dotnet_diagnostic.SA1111.severity = error # Closing parenthesis must be on line of last parameter -dotnet_diagnostic.SA1112.severity = error # Closing parenthesis must be on line of opening parenthesis -dotnet_diagnostic.SA1113.severity = error # Comma must be on same line as previous parameter -dotnet_diagnostic.SA1114.severity = error # Parameter list must follow declaration -dotnet_diagnostic.SA1115.severity = error # Parameter must follow comma -dotnet_diagnostic.SA1116.severity = error # Split parameters must start on line after declaration -dotnet_diagnostic.SA1117.severity = error # Parameters must be on same line or separate lines -dotnet_diagnostic.SA1118.severity = error # Parameter must not span multiple lines -dotnet_diagnostic.SA1120.severity = error # Comments must contain text -dotnet_diagnostic.SA1121.severity = error # Use built-in type alias -dotnet_diagnostic.SA1122.severity = error # Use string.Empty for empty strings -dotnet_diagnostic.SA1123.severity = error # Do not place regions within elements -dotnet_diagnostic.SA1124.severity = error # Do not use regions -dotnet_diagnostic.SA1125.severity = error # Use shorthand for nullable types -dotnet_diagnostic.SA1127.severity = error # Generic type constraints must be on own line -dotnet_diagnostic.SA1128.severity = error # Constructor initializer must be on own line -dotnet_diagnostic.SA1129.severity = error # Do not use default value type constructor -dotnet_diagnostic.SA1130.severity = error # Use lambda syntax -dotnet_diagnostic.SA1131.severity = error # Use readable conditions -dotnet_diagnostic.SA1132.severity = error # Do not combine fields -dotnet_diagnostic.SA1133.severity = error # Do not combine attributes -dotnet_diagnostic.SA1134.severity = error # Attributes must not share line -dotnet_diagnostic.SA1135.severity = error # Using directives must be qualified -dotnet_diagnostic.SA1136.severity = error # Enum values should be on separate lines -dotnet_diagnostic.SA1137.severity = error # Elements should have the same indentation -dotnet_diagnostic.SA1139.severity = error # Use literal suffix notation instead of casting +dotnet_diagnostic.SA1100.severity = error # Do not prefix calls with base unless local implementation exists +dotnet_diagnostic.SA1101.severity = none # Prefix local calls with this +dotnet_diagnostic.SA1102.severity = error # Query clause must follow previous clause +dotnet_diagnostic.SA1103.severity = error # Query clauses must be on same line or separate lines +dotnet_diagnostic.SA1104.severity = error # Query clause must begin on new line when previous clause spans multiple lines +dotnet_diagnostic.SA1105.severity = error # Query clauses spanning multiple lines must begin on own line +dotnet_diagnostic.SA1106.severity = error # Code must not contain empty statements +dotnet_diagnostic.SA1107.severity = error # Code must not contain multiple statements on one line +dotnet_diagnostic.SA1108.severity = error # Block statements must not contain embedded comments +dotnet_diagnostic.SA1110.severity = error # Opening parenthesis or bracket must be on declaration line +dotnet_diagnostic.SA1111.severity = error # Closing parenthesis must be on line of last parameter +dotnet_diagnostic.SA1112.severity = error # Closing parenthesis must be on line of opening parenthesis +dotnet_diagnostic.SA1113.severity = error # Comma must be on same line as previous parameter +dotnet_diagnostic.SA1114.severity = error # Parameter list must follow declaration +dotnet_diagnostic.SA1115.severity = error # Parameter must follow comma +dotnet_diagnostic.SA1116.severity = error # Split parameters must start on line after declaration +dotnet_diagnostic.SA1117.severity = error # Parameters must be on same line or separate lines +dotnet_diagnostic.SA1118.severity = error # Parameter must not span multiple lines +dotnet_diagnostic.SA1120.severity = error # Comments must contain text +dotnet_diagnostic.SA1121.severity = error # Use built-in type alias +dotnet_diagnostic.SA1122.severity = error # Use string.Empty for empty strings +dotnet_diagnostic.SA1123.severity = error # Do not place regions within elements +dotnet_diagnostic.SA1124.severity = error # Do not use regions +dotnet_diagnostic.SA1125.severity = error # Use shorthand for nullable types +dotnet_diagnostic.SA1127.severity = error # Generic type constraints must be on own line +dotnet_diagnostic.SA1128.severity = error # Constructor initializer must be on own line +dotnet_diagnostic.SA1129.severity = error # Do not use default value type constructor +dotnet_diagnostic.SA1130.severity = error # Use lambda syntax +dotnet_diagnostic.SA1131.severity = error # Use readable conditions +dotnet_diagnostic.SA1132.severity = error # Do not combine fields +dotnet_diagnostic.SA1133.severity = error # Do not combine attributes +dotnet_diagnostic.SA1134.severity = error # Attributes must not share line +dotnet_diagnostic.SA1135.severity = error # Using directives must be qualified +dotnet_diagnostic.SA1136.severity = error # Enum values should be on separate lines +dotnet_diagnostic.SA1137.severity = error # Elements should have the same indentation +dotnet_diagnostic.SA1139.severity = error # Use literal suffix notation instead of casting ################### # StyleCop Analyzers (SA) - Ordering Rules ################### -dotnet_diagnostic.SA1200.severity = none # Using directives must be placed correctly -dotnet_diagnostic.SA1201.severity = error # Elements must appear in the correct order -dotnet_diagnostic.SA1202.severity = error # Elements must be ordered by access -dotnet_diagnostic.SA1203.severity = error # Constants must appear before fields -dotnet_diagnostic.SA1204.severity = error # Static elements must appear before instance elements -dotnet_diagnostic.SA1205.severity = error # Partial elements must declare access -dotnet_diagnostic.SA1206.severity = error # Declaration keywords must follow order -dotnet_diagnostic.SA1207.severity = error # Protected must come before internal -dotnet_diagnostic.SA1208.severity = error # System using directives must be placed before other using directives -dotnet_diagnostic.SA1209.severity = error # Using alias directives must be placed after other using directives -dotnet_diagnostic.SA1210.severity = error # Using directives must be ordered alphabetically by namespace -dotnet_diagnostic.SA1211.severity = error # Using alias directives must be ordered alphabetically by alias name -dotnet_diagnostic.SA1212.severity = error # Property accessors must follow order -dotnet_diagnostic.SA1213.severity = error # Event accessors must follow order -dotnet_diagnostic.SA1214.severity = error # Readonly elements must appear before non-readonly elements -dotnet_diagnostic.SA1216.severity = error # Using static directives must be placed at the correct location -dotnet_diagnostic.SA1217.severity = error # Using static directives must be ordered alphabetically +dotnet_diagnostic.SA1200.severity = none # Using directives must be placed correctly +dotnet_diagnostic.SA1201.severity = error # Elements must appear in the correct order +dotnet_diagnostic.SA1202.severity = error # Elements must be ordered by access +dotnet_diagnostic.SA1203.severity = error # Constants must appear before fields +dotnet_diagnostic.SA1204.severity = error # Static elements must appear before instance elements +dotnet_diagnostic.SA1205.severity = error # Partial elements must declare access +dotnet_diagnostic.SA1206.severity = error # Declaration keywords must follow order +dotnet_diagnostic.SA1207.severity = error # Protected must come before internal +dotnet_diagnostic.SA1208.severity = error # System using directives must be placed before other using directives +dotnet_diagnostic.SA1209.severity = error # Using alias directives must be placed after other using directives +dotnet_diagnostic.SA1210.severity = error # Using directives must be ordered alphabetically by namespace +dotnet_diagnostic.SA1211.severity = error # Using alias directives must be ordered alphabetically by alias name +dotnet_diagnostic.SA1212.severity = error # Property accessors must follow order +dotnet_diagnostic.SA1213.severity = error # Event accessors must follow order +dotnet_diagnostic.SA1214.severity = error # Readonly elements must appear before non-readonly elements +dotnet_diagnostic.SA1216.severity = error # Using static directives must be placed at the correct location +dotnet_diagnostic.SA1217.severity = error # Using static directives must be ordered alphabetically ################### # StyleCop Analyzers (SA) - Naming Rules ################### -dotnet_diagnostic.SA1300.severity = error # Element must begin with upper-case letter -dotnet_diagnostic.SA1302.severity = error # Interface names must begin with I -dotnet_diagnostic.SA1303.severity = error # Const field names must begin with upper-case letter -dotnet_diagnostic.SA1304.severity = error # Non-private readonly fields must begin with upper-case letter -dotnet_diagnostic.SA1306.severity = none # Field names must begin with lower-case letter -dotnet_diagnostic.SA1307.severity = error # Accessible fields must begin with upper-case letter -dotnet_diagnostic.SA1308.severity = error # Variable names must not be prefixed -dotnet_diagnostic.SA1309.severity = none # Field names must not begin with underscore -dotnet_diagnostic.SA1310.severity = error # Field names must not contain underscore -dotnet_diagnostic.SA1311.severity = none # Static readonly fields must begin with upper-case letter -dotnet_diagnostic.SA1312.severity = error # Variable names must begin with lower-case letter -dotnet_diagnostic.SA1313.severity = error # Parameter names must begin with lower-case letter -dotnet_diagnostic.SA1314.severity = error # Type parameter names must begin with T -dotnet_diagnostic.SA1316.severity = none # Tuple element names should use correct casing +dotnet_diagnostic.SA1300.severity = error # Element must begin with upper-case letter +dotnet_diagnostic.SA1302.severity = error # Interface names must begin with I +dotnet_diagnostic.SA1303.severity = error # Const field names must begin with upper-case letter +dotnet_diagnostic.SA1304.severity = error # Non-private readonly fields must begin with upper-case letter +dotnet_diagnostic.SA1306.severity = none # Field names must begin with lower-case letter +dotnet_diagnostic.SA1307.severity = error # Accessible fields must begin with upper-case letter +dotnet_diagnostic.SA1308.severity = error # Variable names must not be prefixed +dotnet_diagnostic.SA1309.severity = none # Field names must not begin with underscore +dotnet_diagnostic.SA1310.severity = error # Field names must not contain underscore +dotnet_diagnostic.SA1311.severity = none # Static readonly fields must begin with upper-case letter +dotnet_diagnostic.SA1312.severity = error # Variable names must begin with lower-case letter +dotnet_diagnostic.SA1313.severity = error # Parameter names must begin with lower-case letter +dotnet_diagnostic.SA1314.severity = error # Type parameter names must begin with T +dotnet_diagnostic.SA1316.severity = none # Tuple element names should use correct casing ################### # StyleCop Analyzers (SA) - Maintainability Rules ################### -dotnet_diagnostic.SA1119.severity = error # Statement must not use unnecessary parenthesis -dotnet_diagnostic.SA1400.severity = error # Access modifier must be declared -dotnet_diagnostic.SA1401.severity = error # Fields must be private -dotnet_diagnostic.SA1402.severity = error # File may only contain a single type -dotnet_diagnostic.SA1403.severity = error # File may only contain a single namespace -dotnet_diagnostic.SA1404.severity = error # Code analysis suppression must have justification -dotnet_diagnostic.SA1405.severity = error # Debug.Assert must provide message text -dotnet_diagnostic.SA1406.severity = error # Debug.Fail must provide message text -dotnet_diagnostic.SA1407.severity = error # Arithmetic expressions must declare precedence -dotnet_diagnostic.SA1408.severity = error # Conditional expressions must declare precedence -dotnet_diagnostic.SA1410.severity = error # Remove delegate parenthesis when possible -dotnet_diagnostic.SA1411.severity = error # Attribute constructor must not use unnecessary parenthesis -dotnet_diagnostic.SA1413.severity = none # Use trailing commas in multi-line initializers +dotnet_diagnostic.SA1119.severity = error # Statement must not use unnecessary parenthesis +dotnet_diagnostic.SA1400.severity = error # Access modifier must be declared +dotnet_diagnostic.SA1401.severity = error # Fields must be private +dotnet_diagnostic.SA1402.severity = error # File may only contain a single type +dotnet_diagnostic.SA1403.severity = error # File may only contain a single namespace +dotnet_diagnostic.SA1404.severity = error # Code analysis suppression must have justification +dotnet_diagnostic.SA1405.severity = error # Debug.Assert must provide message text +dotnet_diagnostic.SA1406.severity = error # Debug.Fail must provide message text +dotnet_diagnostic.SA1407.severity = error # Arithmetic expressions must declare precedence +dotnet_diagnostic.SA1408.severity = error # Conditional expressions must declare precedence +dotnet_diagnostic.SA1410.severity = error # Remove delegate parenthesis when possible +dotnet_diagnostic.SA1411.severity = error # Attribute constructor must not use unnecessary parenthesis +dotnet_diagnostic.SA1413.severity = none # Use trailing commas in multi-line initializers ################### # StyleCop Analyzers (SA) - Layout Rules ################### -dotnet_diagnostic.SA1500.severity = error # Braces for multi-line statements must not share line -dotnet_diagnostic.SA1501.severity = error # Statement must not be on single line -dotnet_diagnostic.SA1502.severity = error # Element must not be on single line -dotnet_diagnostic.SA1503.severity = error # Braces must not be omitted -dotnet_diagnostic.SA1504.severity = error # All accessors must be single-line or multi-line -dotnet_diagnostic.SA1505.severity = none # Opening braces must not be followed by blank line -dotnet_diagnostic.SA1506.severity = error # Element documentation headers must not be followed by blank line -dotnet_diagnostic.SA1507.severity = error # Code must not contain multiple blank lines in a row -dotnet_diagnostic.SA1508.severity = error # Closing braces must not be preceded by blank line -dotnet_diagnostic.SA1509.severity = error # Opening braces must not be preceded by blank line -dotnet_diagnostic.SA1510.severity = error # Chained statement blocks must not be preceded by blank line -dotnet_diagnostic.SA1511.severity = error # While-do footer must not be preceded by blank line -dotnet_diagnostic.SA1512.severity = error # Single-line comments must not be followed by blank line -dotnet_diagnostic.SA1513.severity = error # Closing brace must be followed by blank line -dotnet_diagnostic.SA1514.severity = none # Element documentation header must be preceded by blank line -dotnet_diagnostic.SA1515.severity = error # Single-line comment must be preceded by blank line -dotnet_diagnostic.SA1516.severity = error # Elements must be separated by blank line -dotnet_diagnostic.SA1517.severity = error # Code must not contain blank lines at start of file -dotnet_diagnostic.SA1518.severity = error # Use line endings correctly at end of file -dotnet_diagnostic.SA1519.severity = error # Braces must not be omitted from multi-line child statement -dotnet_diagnostic.SA1520.severity = error # Use braces consistently +dotnet_diagnostic.SA1500.severity = error # Braces for multi-line statements must not share line +dotnet_diagnostic.SA1501.severity = error # Statement must not be on single line +dotnet_diagnostic.SA1502.severity = error # Element must not be on single line +dotnet_diagnostic.SA1503.severity = error # Braces must not be omitted +dotnet_diagnostic.SA1504.severity = error # All accessors must be single-line or multi-line +dotnet_diagnostic.SA1505.severity = none # Opening braces must not be followed by blank line +dotnet_diagnostic.SA1506.severity = error # Element documentation headers must not be followed by blank line +dotnet_diagnostic.SA1507.severity = error # Code must not contain multiple blank lines in a row +dotnet_diagnostic.SA1508.severity = error # Closing braces must not be preceded by blank line +dotnet_diagnostic.SA1509.severity = error # Opening braces must not be preceded by blank line +dotnet_diagnostic.SA1510.severity = error # Chained statement blocks must not be preceded by blank line +dotnet_diagnostic.SA1511.severity = error # While-do footer must not be preceded by blank line +dotnet_diagnostic.SA1512.severity = error # Single-line comments must not be followed by blank line +dotnet_diagnostic.SA1513.severity = error # Closing brace must be followed by blank line +dotnet_diagnostic.SA1514.severity = none # Element documentation header must be preceded by blank line +dotnet_diagnostic.SA1515.severity = error # Single-line comment must be preceded by blank line +dotnet_diagnostic.SA1516.severity = error # Elements must be separated by blank line +dotnet_diagnostic.SA1517.severity = error # Code must not contain blank lines at start of file +dotnet_diagnostic.SA1518.severity = error # Use line endings correctly at end of file +dotnet_diagnostic.SA1519.severity = error # Braces must not be omitted from multi-line child statement +dotnet_diagnostic.SA1520.severity = error # Use braces consistently ################### # StyleCop Analyzers (SA) - Documentation Rules ################### -dotnet_diagnostic.SA1600.severity = error # Elements must be documented -dotnet_diagnostic.SA1601.severity = error # Partial elements must be documented -dotnet_diagnostic.SA1602.severity = error # Enumeration items must be documented -dotnet_diagnostic.SA1604.severity = error # Element documentation must have summary -dotnet_diagnostic.SA1605.severity = error # Partial element documentation must have summary -dotnet_diagnostic.SA1606.severity = error # Element documentation must have summary text -dotnet_diagnostic.SA1607.severity = error # Partial element documentation must have summary text -dotnet_diagnostic.SA1608.severity = error # Element documentation must not have default summary -dotnet_diagnostic.SA1610.severity = error # Property documentation must have value text -dotnet_diagnostic.SA1611.severity = error # Element parameters must be documented -dotnet_diagnostic.SA1612.severity = error # Element parameter documentation must match element parameters -dotnet_diagnostic.SA1613.severity = error # Element parameter documentation must declare parameter name -dotnet_diagnostic.SA1614.severity = error # Element parameter documentation must have text -dotnet_diagnostic.SA1615.severity = error # Element return value must be documented -dotnet_diagnostic.SA1616.severity = error # Element return value documentation must have text -dotnet_diagnostic.SA1617.severity = error # Void return value must not be documented -dotnet_diagnostic.SA1618.severity = error # Generic type parameters must be documented -dotnet_diagnostic.SA1619.severity = error # Generic type parameters must be documented partial class -dotnet_diagnostic.SA1620.severity = error # Generic type parameter documentation must match type parameters -dotnet_diagnostic.SA1621.severity = error # Generic type parameter documentation must declare parameter name -dotnet_diagnostic.SA1622.severity = error # Generic type parameter documentation must have text -dotnet_diagnostic.SA1623.severity = error # Property summary documentation must match accessors -dotnet_diagnostic.SA1624.severity = error # Property summary documentation must omit set accessor with restricted access -dotnet_diagnostic.SA1625.severity = error # Element documentation must not be copied and pasted -dotnet_diagnostic.SA1626.severity = error # Single-line comments must not use documentation style slashes -dotnet_diagnostic.SA1627.severity = error # Documentation text must not be empty -dotnet_diagnostic.SA1629.severity = error # Documentation text must end with a period -dotnet_diagnostic.SA1633.severity = error # File must have header -dotnet_diagnostic.SA1634.severity = error # File header must show copyright -dotnet_diagnostic.SA1635.severity = error # File header must have copyright text -dotnet_diagnostic.SA1636.severity = error # File header copyright text must match -dotnet_diagnostic.SA1637.severity = none # File header must contain file name -dotnet_diagnostic.SA1638.severity = none # File header file name documentation must match file name -dotnet_diagnostic.SA1640.severity = error # File header must have valid company text -dotnet_diagnostic.SA1641.severity = error # File header company name text must match -dotnet_diagnostic.SA1642.severity = error # Constructor summary documentation must begin with standard text -dotnet_diagnostic.SA1643.severity = error # Destructor summary documentation must begin with standard text -dotnet_diagnostic.SA1649.severity = error # File name must match type name -dotnet_diagnostic.SA1651.severity = error # Do not use placeholder elements +dotnet_diagnostic.SA1600.severity = error # Elements must be documented +dotnet_diagnostic.SA1601.severity = error # Partial elements must be documented +dotnet_diagnostic.SA1602.severity = error # Enumeration items must be documented +dotnet_diagnostic.SA1604.severity = error # Element documentation must have summary +dotnet_diagnostic.SA1605.severity = error # Partial element documentation must have summary +dotnet_diagnostic.SA1606.severity = error # Element documentation must have summary text +dotnet_diagnostic.SA1607.severity = error # Partial element documentation must have summary text +dotnet_diagnostic.SA1608.severity = error # Element documentation must not have default summary +dotnet_diagnostic.SA1610.severity = error # Property documentation must have value text +dotnet_diagnostic.SA1611.severity = error # Element parameters must be documented +dotnet_diagnostic.SA1612.severity = error # Element parameter documentation must match element parameters +dotnet_diagnostic.SA1613.severity = error # Element parameter documentation must declare parameter name +dotnet_diagnostic.SA1614.severity = error # Element parameter documentation must have text +dotnet_diagnostic.SA1615.severity = error # Element return value must be documented +dotnet_diagnostic.SA1616.severity = error # Element return value documentation must have text +dotnet_diagnostic.SA1617.severity = error # Void return value must not be documented +dotnet_diagnostic.SA1618.severity = error # Generic type parameters must be documented +dotnet_diagnostic.SA1619.severity = error # Generic type parameters must be documented partial class +dotnet_diagnostic.SA1620.severity = error # Generic type parameter documentation must match type parameters +dotnet_diagnostic.SA1621.severity = error # Generic type parameter documentation must declare parameter name +dotnet_diagnostic.SA1622.severity = error # Generic type parameter documentation must have text +dotnet_diagnostic.SA1623.severity = error # Property summary documentation must match accessors +dotnet_diagnostic.SA1624.severity = error # Property summary documentation must omit set accessor with restricted access +dotnet_diagnostic.SA1625.severity = error # Element documentation must not be copied and pasted +dotnet_diagnostic.SA1626.severity = error # Single-line comments must not use documentation style slashes +dotnet_diagnostic.SA1627.severity = error # Documentation text must not be empty +dotnet_diagnostic.SA1629.severity = error # Documentation text must end with a period +dotnet_diagnostic.SA1633.severity = error # File must have header +dotnet_diagnostic.SA1634.severity = error # File header must show copyright +dotnet_diagnostic.SA1635.severity = error # File header must have copyright text +dotnet_diagnostic.SA1636.severity = error # File header copyright text must match +dotnet_diagnostic.SA1637.severity = none # File header must contain file name +dotnet_diagnostic.SA1638.severity = none # File header file name documentation must match file name +dotnet_diagnostic.SA1640.severity = error # File header must have valid company text +dotnet_diagnostic.SA1641.severity = error # File header company name text must match +dotnet_diagnostic.SA1642.severity = error # Constructor summary documentation must begin with standard text +dotnet_diagnostic.SA1643.severity = error # Destructor summary documentation must begin with standard text +dotnet_diagnostic.SA1649.severity = error # File name must match type name +dotnet_diagnostic.SA1651.severity = error # Do not use placeholder elements ################### # StyleCop Alternative Analyzers (SX) - Alternative Rules ################### -dotnet_diagnostic.SX1101.severity = error # Do not prefix local members with this -dotnet_diagnostic.SX1309.severity = error # Field names must begin with underscore -dotnet_diagnostic.SX1623.severity = none # Property summary documentation must match accessors (alternative) +dotnet_diagnostic.SX1101.severity = error # Do not prefix local members with this +dotnet_diagnostic.SX1309.severity = error # Field names must begin with underscore +dotnet_diagnostic.SX1623.severity = none # Property summary documentation must match accessors (alternative) ############################################# # NUnit Analyzers @@ -800,6 +800,138 @@ dotnet_diagnostic.NUnit3004.severity = error # Field should be disposed in TearD dotnet_diagnostic.NUnit4001.severity = error # Simplify the Values attribute dotnet_diagnostic.NUnit4002.severity = error # Use Specific constraint +################### +# Trimming Analyzer Warnings (IL2001 - IL2123) +# See: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/ +################### +dotnet_diagnostic.IL2001.severity = error # Type in UnreferencedCode attribute doesn't have matching RequiresUnreferencedCode +dotnet_diagnostic.IL2002.severity = error # Method with RequiresUnreferencedCode called from code without that attribute +dotnet_diagnostic.IL2003.severity = error # RequiresUnreferencedCode attribute is only supported on methods +dotnet_diagnostic.IL2004.severity = error # Incorrect RequiresUnreferencedCode signature +dotnet_diagnostic.IL2005.severity = error # Could not resolve dependency assembly +dotnet_diagnostic.IL2007.severity = error # Could not process embedded resource +dotnet_diagnostic.IL2008.severity = error # Could not find type in assembly +dotnet_diagnostic.IL2009.severity = error # Could not find method in type +dotnet_diagnostic.IL2010.severity = error # Invalid value for PreserveDependencyAttribute +dotnet_diagnostic.IL2011.severity = error # Unknown body modification +dotnet_diagnostic.IL2012.severity = error # Could not find field in type +dotnet_diagnostic.IL2013.severity = error # Substitution file contains invalid XML +dotnet_diagnostic.IL2014.severity = error # Missing substitution file +dotnet_diagnostic.IL2015.severity = error # Invalid XML encountered in substitution file +dotnet_diagnostic.IL2016.severity = error # Could not find type from substitution XML +dotnet_diagnostic.IL2017.severity = error # Could not find method in type specified in substitution XML +dotnet_diagnostic.IL2018.severity = error # Could not find field in type specified in substitution XML +dotnet_diagnostic.IL2019.severity = error # Could not find interface implementation in type +dotnet_diagnostic.IL2022.severity = error # Type in DynamicallyAccessedMembers attribute doesn't have matching DynamicallyAccessedMembers annotation +dotnet_diagnostic.IL2023.severity = error # Method returning DynamicallyAccessedMembers annotated type requires the same annotation +dotnet_diagnostic.IL2024.severity = error # Multiple DynamicallyAccessedMembers annotations on a member are not supported +dotnet_diagnostic.IL2025.severity = error # Duplicate preserve attribute +dotnet_diagnostic.IL2026.severity = error # Using member annotated with RequiresUnreferencedCode +dotnet_diagnostic.IL2027.severity = error # RequiresUnreferencedCodeAttribute is only supported on methods and constructors +dotnet_diagnostic.IL2028.severity = error # Invalid RequiresUnreferencedCode attribute usage +dotnet_diagnostic.IL2029.severity = error # RequiresUnreferencedCode attribute on type is not supported +dotnet_diagnostic.IL2030.severity = error # Dynamic invocation of method requiring unreferenced code is not safe +dotnet_diagnostic.IL2031.severity = error # Could not resolve dependency assembly from embedded resource +dotnet_diagnostic.IL2032.severity = error # Error reading debug symbols +dotnet_diagnostic.IL2033.severity = error # Trying to modify a sealed type +dotnet_diagnostic.IL2034.severity = error # Value passed to the implicit 'this' parameter does not satisfy 'DynamicallyAccessedMembersAttribute' requirements +dotnet_diagnostic.IL2035.severity = error # Unrecognized value passed to the parameter of method with 'DynamicallyAccessedMembersAttribute' requirements +dotnet_diagnostic.IL2036.severity = error # Interface implementation has different DynamicallyAccessedMembers annotations than interface +dotnet_diagnostic.IL2037.severity = error # BaseType annotation doesn't match +dotnet_diagnostic.IL2038.severity = error # Derived type doesn't have matching DynamicallyAccessedMembers annotation +dotnet_diagnostic.IL2039.severity = error # Implementation method doesn't have matching DynamicallyAccessedMembers annotation +dotnet_diagnostic.IL2040.severity = error # Interface member doesn't have matching DynamicallyAccessedMembers annotation +dotnet_diagnostic.IL2041.severity = error # GetType call on DynamicallyAccessedMembers annotated generic parameter +dotnet_diagnostic.IL2042.severity = error # The DynamicallyAccessedMembersAttribute value used in a custom attribute is not compatible +dotnet_diagnostic.IL2043.severity = error # DynamicallyAccessedMembersAttribute on property conflicts with base property +dotnet_diagnostic.IL2044.severity = error # DynamicallyAccessedMembersAttribute on event conflicts with base event +dotnet_diagnostic.IL2045.severity = error # Field type doesn't satisfy 'DynamicallyAccessedMembersAttribute' requirements +dotnet_diagnostic.IL2046.severity = error # Trimmer couldn't find PreserveBaseOverridesAttribute on a method +dotnet_diagnostic.IL2048.severity = error # Internal attribute couldn't be removed +dotnet_diagnostic.IL2049.severity = error # Could not process data format message +dotnet_diagnostic.IL2050.severity = error # Correctness of COM interop cannot be guaranteed after trimming +dotnet_diagnostic.IL2051.severity = error # COM related type is trimmed +dotnet_diagnostic.IL2052.severity = error # Resolving member reference for P/Invoke into type that is trimmed +dotnet_diagnostic.IL2053.severity = error # Target method is trimmed +dotnet_diagnostic.IL2054.severity = error # Generic constraint type is annotated with DynamicallyAccessedMembersAttribute which requires unreferenced code +dotnet_diagnostic.IL2055.severity = error # Type implements COM visible type but has no GUID +dotnet_diagnostic.IL2056.severity = error # Generic parameter with DynamicallyAccessedMembers annotation is not publicly visible +dotnet_diagnostic.IL2057.severity = error # Unrecognized value passed to the parameter of method with DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2058.severity = error # Parameter types of method doesn't have matching DynamicallyAccessedMembers annotation +dotnet_diagnostic.IL2059.severity = error # Unrecognized reflection pattern +dotnet_diagnostic.IL2060.severity = error # Unrecognized value passed to parameter with DynamicallyAccessedMembersAttribute +dotnet_diagnostic.IL2061.severity = error # Value passed to implicit this parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2062.severity = error # Value passed to parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2063.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2064.severity = error # Value assigned to field doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2065.severity = error # Value passed to implicit this parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2066.severity = error # Value stored in field doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2067.severity = error # Value passed to implicit this parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2068.severity = error # Value passed to parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2069.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2070.severity = error # Value stored in field doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2071.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2072.severity = error # Value passed to parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2073.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2074.severity = error # Value stored in field doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2075.severity = error # Value passed to implicit this parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2076.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2077.severity = error # Value passed to parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2078.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2079.severity = error # Value stored in field doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2080.severity = error # Value passed to implicit this parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2081.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2082.severity = error # Value passed to parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2083.severity = error # Value stored in field doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2084.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2085.severity = error # Value passed to implicit this parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2087.severity = error # Value passed to parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2088.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2089.severity = error # Value stored in field doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2090.severity = error # Value passed to implicit this parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2091.severity = error # Target generic argument doesn't satisfy 'DynamicallyAccessedMembersAttribute' requirements +dotnet_diagnostic.IL2092.severity = error # Value passed to generic parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2093.severity = error # Value stored in field doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2094.severity = error # DynamicallyAccessedMembers on 'this' parameter doesn't match overridden member +dotnet_diagnostic.IL2095.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2096.severity = error # Calling method on statically typed generic instance requires unreferenced code +dotnet_diagnostic.IL2097.severity = error # Value passed to parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2098.severity = error # Value stored in field doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2099.severity = error # Value returned from method doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2100.severity = error # XML stream doesn't conform to the schema +dotnet_diagnostic.IL2101.severity = error # Embedded XML in assembly couldn't be loaded +dotnet_diagnostic.IL2102.severity = error # Invalid warning number passed to UnconditionalSuppressMessage +dotnet_diagnostic.IL2103.severity = error # Value passed to the 'propertyAccessExpression' parameter doesn't satisfy DynamicallyAccessedMembersAttribute requirements +dotnet_diagnostic.IL2104.severity = error # Assembly that was specified through a custom step +dotnet_diagnostic.IL2105.severity = error # Type from a custom step that couldn't be loaded +dotnet_diagnostic.IL2106.severity = error # Method from a custom step that couldn't be loaded +dotnet_diagnostic.IL2107.severity = error # Methods in types that derive from RemotingClientProxy cannot be statically determined +dotnet_diagnostic.IL2108.severity = error # Invalid scope for UnconditionalSuppressMessage +dotnet_diagnostic.IL2109.severity = error # Method doesn't have matching DynamicallyAccessedMembers annotation +dotnet_diagnostic.IL2110.severity = error # Invalid member name in UnconditionalSuppressMessage +dotnet_diagnostic.IL2111.severity = error # Method with parameters or return value with DynamicallyAccessedMembersAttribute is not supported +dotnet_diagnostic.IL2112.severity = error # Reflection call to method with DynamicallyAccessedMembersAttribute requirements cannot be statically analyzed +dotnet_diagnostic.IL2113.severity = error # DynamicallyAccessedMembers on type references Type.MakeGenericType with different requirements +dotnet_diagnostic.IL2114.severity = error # DynamicallyAccessedMembers mismatch on signature types +dotnet_diagnostic.IL2115.severity = error # DynamicallyAccessedMembers on type or base types references member which requires unreferenced code +dotnet_diagnostic.IL2116.severity = error # DynamicallyAccessedMembers on parameter types doesn't match overridden parameter +dotnet_diagnostic.IL2117.severity = error # Methods with DynamicallyAccessedMembersAttribute annotations cannot be replaced +dotnet_diagnostic.IL2122.severity = error # Reflection call to method with UnreferencedCode attribute cannot be statically analyzed +dotnet_diagnostic.IL2123.severity = error # DynamicallyAccessedMembers on method or parameter doesn't match overridden member + +################### +# AOT Analyzer Warnings (IL3xxx) +# See: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/warnings/ +################### +dotnet_diagnostic.IL3050.severity = error # Using member annotated with RequiresDynamicCode +dotnet_diagnostic.IL3051.severity = error # RequiresDynamicCode attribute is only supported on methods and constructors +dotnet_diagnostic.IL3052.severity = error # RequiresDynamicCode attribute on type is not supported +dotnet_diagnostic.IL3053.severity = error # Assembly has RequiresDynamicCode attribute +dotnet_diagnostic.IL3054.severity = error # Generic expansion in type requires dynamic code +dotnet_diagnostic.IL3055.severity = error # MakeGenericType on non-supported type requires dynamic code +dotnet_diagnostic.IL3056.severity = error # MakeGenericMethod on non-supported method requires dynamic code +dotnet_diagnostic.IL3057.severity = error # Reflection access to generic parameter requires dynamic code + ############################################# # C++ Files ############################################# diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 14f8a63357..0d772a6a28 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -41,9 +41,21 @@ Invoke-WebRequest -Uri https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-ins ### Solution Files -* Main solution: `src/ReactiveUI.sln` (repository `root/src` directory) +* Main solution: `src/reactiveui.slnx` (repository `root/src` directory) * Integration tests: `integrationtests/` directory contains platform-specific solutions. These are not required for most tasks to compile. +**About SLNX Format:** + +This repository uses **SLNX** (XML-based solution format) introduced in Visual Studio 2022 17.10+, instead of the legacy `.sln` format. + +**Key characteristics:** +- Structured XML format vs. proprietary text format +- Better support for complex multi-platform projects like ReactiveUI +- Full compatibility with `dotnet` CLI (works identically to .sln) +- Requires Visual Studio 2022 17.10+ or JetBrains Rider 2024.1+ for IDE support + +All commands in this document reference `src/reactiveui.slnx`. + --- ## 🛠️ Build & Test Commands @@ -67,24 +79,42 @@ dotnet workload restore cd .. # Restore NuGet packages -dotnet restore src/ReactiveUI.sln +dotnet restore src/reactiveui.slnx # Build the solution (requires Windows for platform-specific targets) -dotnet build src/ReactiveUI.sln -c Release -warnaserror +dotnet build src/reactiveui.slnx -c Release -warnaserror ``` ### Test Commands (Microsoft Testing Platform) **CRITICAL:** This repository uses MTP configured in `src/global.json`. All TUnit-specific arguments must be passed after `--`. +**Microsoft Testing Platform (MTP) Overview:** + +MTP is the modern test execution platform replacing VSTest, providing: +- Native integration with `dotnet test` command +- Better performance and modern architecture for .NET 6.0+ +- Enhanced test filtering and parallel execution control +- Required for TUnit framework (ReactiveUI's chosen test framework) + +**Key Differences from VSTest:** +- Arguments passed AFTER `--`: `dotnet test -- --treenode-filter "..."` +- Configured via `global.json` (specifies "Microsoft.Testing.Platform") +- Test settings in `testconfig.json` (parallel execution, coverage format) + +**Best Practices:** +- **Never use `--no-build`** - always build before testing to avoid stale binaries +- Use `--output Detailed` BEFORE `--` to see Console.WriteLine output +- TUnit runs non-parallel (`"parallel": false`) to prevent test interference + **Note:** Commands below assume repository root as working directory. Use `src/` prefix for paths. ```powershell # Run all tests -dotnet test src/ReactiveUI.sln -c Release --no-build +dotnet test src/reactiveui.slnx -c Release # Run tests with code coverage (Microsoft Code Coverage) -dotnet test src/ReactiveUI.sln -- --coverage --coverage-output-format cobertura +dotnet test src/reactiveui.slnx --coverage --coverage-output-format cobertura # Run specific test project dotnet test --project src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj @@ -96,16 +126,16 @@ dotnet test --project src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --tr dotnet test --project src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --treenode-filter "/*/*/MyClassName/*" # Filter by test property (e.g., Category) -dotnet test src/ReactiveUI.sln -- --treenode-filter "/*/*/*/*[Category=Integration]" +dotnet test src/reactiveui.slnx -- --treenode-filter "/*/*/*/*[Category=Integration]" # List all available tests dotnet test --project src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --list-tests # Fail fast (stop on first failure) -dotnet test src/ReactiveUI.sln -- --fail-fast +dotnet test src/reactiveui.slnx -- --fail-fast # Generate TRX report with coverage -dotnet test src/ReactiveUI.sln -- --coverage --coverage-output-format cobertura --report-trx --output Detailed +dotnet test src/reactiveui.slnx --coverage --coverage-output-format cobertura -- --report-trx --output Detailed ``` **TUnit Treenode-Filter Syntax:** @@ -118,10 +148,14 @@ Examples: - All tests in namespace: `--treenode-filter "/*/MyNamespace/*/*"` - Filter by property: `--treenode-filter "/*/*/*/*[Category=Integration]"` -**Key TUnit Command-Line Flags:** -- `--treenode-filter` - Filter tests by path or properties +**Key Command-Line Flags:** + +**dotnet test flags (BEFORE `--`):** - `--coverage` - Enable Microsoft Code Coverage - `--coverage-output-format` - Set format (cobertura, xml, coverage) + +**TUnit flags (AFTER `--`):** +- `--treenode-filter` - Filter tests by path or properties - `--report-trx` - Generate TRX reports - `--output` - Verbosity (Normal or Detailed) - `--list-tests` - Display tests without running @@ -528,11 +562,66 @@ _total = this.WhenAnyValue( ```powershell # Build with warnings as errors (includes StyleCop violations) -dotnet build src/ReactiveUI.sln -c Release -warnaserror +dotnet build src/reactiveui.slnx -c Release -warnaserror ``` **Important:** Style violations will cause build failures. Use an IDE with EditorConfig support (Visual Studio, VS Code, Rider) to automatically format code according to project standards. +### Zero Pragma Policy for Warning Suppression + +**CRITICAL: This project enforces a strict zero pragma policy.** + +* **NO `#pragma warning disable` statements are allowed** in any code files +* **All StyleCop analyzer warnings (SA****) MUST be fixed**, never suppressed +* **Code analyzer warnings (CA****) may ONLY be suppressed as an absolute last resort** using `[SuppressMessage]` attribute with these requirements: + 1. You have attempted to fix the warning first + 2. Fixing it would make the code worse or is not technically feasible + 3. You use `[SuppressMessage]` attribute (NOT pragma) + 4. You provide a clear, valid justification in the `Justification` parameter + +#### Examples + +```csharp +// ❌ WRONG - Never use pragma directives +#pragma warning disable CA1062 +public void ProcessData(object data) +{ + data.ToString(); // CA1062: Validate parameter is non-null +} +#pragma warning restore CA1062 + +// ✅ CORRECT - Fix the issue +public void ProcessData(object data) +{ + ArgumentNullException.ThrowIfNull(data); + data.ToString(); +} + +// ⚠️ ACCEPTABLE - SuppressMessage with justification (last resort only) +[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", + Justification = "TUnit framework guarantees non-null parameters from test data sources")] +public async Task ValidateConverter(IBindingTypeConverter converter, int expectedAffinity) +{ + var affinity = converter.GetAffinityForObjects(); // converter is never null + await Assert.That(affinity).IsEqualTo(expectedAffinity); +} +``` + +#### Common StyleCop Fixes (Must Be Fixed, Never Suppressed) + +* **SA1202** (Static members before instance members): Reorder class members +* **SA1204** (Static members before non-static): Move static members to top +* **SA1402** (One type per file): Convert file-scoped types to nested private classes +* **SA1611** (Parameter documentation missing): Add `` XML tags +* **SA1615** (Return value documentation missing): Add `` XML tags +* **SA1600** (Elements should be documented): Add XML documentation to public members + +#### Enforcement + +* Builds fail with `-warnaserror` if any analyzer warnings exist +* Any pragma directives will be flagged and rejected during code review +* `SuppressMessage` usage requires clear justification and approval + --- ## 📋 Testing Guidelines diff --git a/.gitignore b/.gitignore index 9602fa91e5..87186c0060 100644 --- a/.gitignore +++ b/.gitignore @@ -450,3 +450,6 @@ src/Tools/ **/*.GeneratedMSBuildEditorConfig.editorconfig /app .dotnet/ + +# Claude Settings +.claude/ diff --git a/CLAUDE.md b/CLAUDE.md index 3a77aab352..0d618e37f8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,27 @@ This project uses **Microsoft Testing Platform (MTP)** with the **TUnit** testin See: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=dotnet-test-with-mtp +### Solution Format: SLNX + +**Note**: This repository uses **SLNX** (XML-based solution format) instead of the legacy SLN format. + +**What is SLNX?** +- Modern XML-based solution file format introduced in Visual Studio 2022 17.10+ +- Replaces the legacy text-based `.sln` format +- More structured and easier to parse programmatically +- Better support for complex multi-platform projects +- Full compatibility with `dotnet` CLI commands + +**Key Differences from .sln:** +- **Format:** Structured XML vs. legacy text format +- **Readability:** Human-readable XML schema vs. proprietary text format +- **Tooling:** Requires VS 2022 17.10+ or Rider 2024.1+ for IDE support +- **CLI:** Works identically with all `dotnet build/test` commands + +**File Location:** `src/reactiveui.slnx` + +All build and test commands in this document reference `reactiveui.slnx`. The file works identically to traditional `.sln` files with dotnet CLI tools. + ### Prerequisites ```powershell @@ -21,7 +42,7 @@ dotnet workload restore cd .. # Restore NuGet packages -dotnet restore ReactiveUI.sln +dotnet restore reactiveui.slnx ``` ### Build Commands @@ -30,27 +51,55 @@ dotnet restore ReactiveUI.sln ```powershell # Build the solution (requires Windows for platform-specific targets) -dotnet build ReactiveUI.sln -c Release +dotnet build reactiveui.slnx -c Release # Build with warnings as errors (includes StyleCop violations) -dotnet build ReactiveUI.sln -c Release -warnaserror +dotnet build reactiveui.slnx -c Release -warnaserror # Clean the solution -dotnet clean ReactiveUI.sln +dotnet clean reactiveui.slnx ``` ### Test Commands (Microsoft Testing Platform) **CRITICAL:** This repository uses MTP configured in `global.json`. All TUnit-specific arguments must be passed after `--`: +**What is Microsoft Testing Platform (MTP)?** + +MTP is the modern test execution platform for .NET, replacing the legacy VSTest platform. It provides: +- **Native integration** with `dotnet test` command +- **Better performance** through optimized test discovery and execution +- **Modern architecture** designed for current .NET versions (6.0+) +- **Enhanced control** over test execution with detailed filtering and reporting + +**Why ReactiveUI uses MTP:** +- Required for TUnit testing framework (modern alternative to xUnit/NUnit) +- Better integration with build systems and CI/CD pipelines +- Improved test isolation and parallel execution control +- Native support for modern .NET features + +**Key Difference from VSTest:** +- MTP arguments are passed AFTER `--` separator: `dotnet test -- --mtp-args` +- VSTest used different command syntax: `dotnet test --vstest-args` +- MTP is configured via `global.json` (see "Key Configuration Files" section) + +**Configuration:** +- `global.json`: Specifies MTP as the test runner (`"Microsoft.Testing.Platform"`) +- `testconfig.json`: Test execution settings (parallel: false, coverage format) +- `Directory.Build.props`: Enables `TestingPlatformDotnetTestSupport` for test projects + +**IMPORTANT Testing Best Practices:** +- **Do NOT use `--no-build` flag** when running tests. Always build before testing to ensure all code changes (including test changes) are compiled. Using `--no-build` can cause tests to run against stale binaries and produce misleading results. +- Use `--output Detailed` to see Console.WriteLine output from tests. This must be placed BEFORE the `--` separator +- TUnit runs tests non-parallel by default in this repository (`"parallel": false` in testconfig.json) to avoid test interference + +**Working Directory:** All test commands must be run from the `./src` folder. + The working folder must be `./src` folder. These commands won't function properly without the correct working folder. ```powershell # Run all tests in the solution -dotnet test --solution ReactiveUI.sln -c Release - -# Run all tests without building first (faster when code hasn't changed) -dotnet test --solution ReactiveUI.sln -c Release --no-build +dotnet test --solution reactiveui.slnx -c Release # Run all tests in a specific project dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj @@ -66,31 +115,31 @@ dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --treeno dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --treenode-filter "/*/MyNamespace/*/*" # Filter by test property (e.g., Category) -dotnet test --solution ReactiveUI.sln -- --treenode-filter "/*/*/*/*[Category=Integration]" +dotnet test --solution reactiveui.slnx -- --treenode-filter "/*/*/*/*[Category=Integration]" # Run tests with code coverage (Microsoft Code Coverage) -dotnet test --solution ReactiveUI.sln -- --coverage --coverage-output-format cobertura +dotnet test --solution reactiveui.slnx --coverage --coverage-output-format cobertura # Run tests with detailed output -dotnet test --solution ReactiveUI.sln -- --output Detailed +dotnet test --solution reactiveui.slnx -- --output Detailed # List all available tests without running them dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --list-tests # Fail fast (stop on first failure) -dotnet test --solution ReactiveUI.sln -- --fail-fast +dotnet test --solution reactiveui.slnx -- --fail-fast # Control parallel test execution -dotnet test --solution ReactiveUI.sln -- --maximum-parallel-tests 4 +dotnet test --solution reactiveui.slnx -- --maximum-parallel-tests 4 # Generate TRX report -dotnet test --solution ReactiveUI.sln -- --report-trx +dotnet test --solution reactiveui.slnx -- --report-trx # Disable logo for cleaner output dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --disable-logo # Combine options: coverage + TRX report + detailed output -dotnet test --solution ReactiveUI.sln -- --coverage --coverage-output-format cobertura --report-trx --output Detailed +dotnet test --solution reactiveui.slnx --coverage --coverage-output-format cobertura -- --report-trx --output Detailed ``` **Alternative: Using `dotnet run` for single project** @@ -115,14 +164,17 @@ The `--treenode-filter` follows the pattern: `/{AssemblyName}/{Namespace}/{Class **Note:** Use single asterisks (`*`) to match segments. Double asterisks (`/**`) are not supported in treenode-filter. -### Key TUnit Command-Line Flags +### Key Command-Line Flags + +**dotnet test flags (BEFORE `--`):** +- `--coverage` - Enable Microsoft Code Coverage +- `--coverage-output-format` - Set coverage format (cobertura, xml, coverage) +**TUnit flags (AFTER `--`):** - `--treenode-filter` - Filter tests by path pattern or properties (syntax: `/{Assembly}/{Namespace}/{Class}/{Method}`) - `--list-tests` - Display available tests without running - `--fail-fast` - Stop after first failure - `--maximum-parallel-tests` - Limit concurrent execution (default: processor count) -- `--coverage` - Enable Microsoft Code Coverage -- `--coverage-output-format` - Set coverage format (cobertura, xml, coverage) - `--report-trx` - Generate TRX format reports - `--output` - Control verbosity (Normal or Detailed) - `--no-progress` - Suppress progress reporting @@ -373,6 +425,64 @@ _total = this.WhenAnyValue( 3. Verify AOT compatibility 4. Ensure no regression in existing tests +## Code Quality and Warning Suppression Policy + +### Zero Pragma Policy + +**CRITICAL: This project enforces a zero pragma policy for warning suppression.** + +- **NO `#pragma warning disable` statements allowed** in any code files +- **All StyleCop analyzer warnings (SA****) MUST be fixed**, not suppressed +- **Code analyzer warnings (CA****) may be suppressed ONLY as a last resort** with the following conditions: + 1. You have attempted to fix the warning first + 2. The fix would make the code worse or is not feasible + 3. You use `[SuppressMessage]` attribute instead of pragma + 4. You provide a clear, valid justification in the `Justification` parameter + +### When Suppression Is Acceptable + +```csharp +// WRONG - Never use pragma +#pragma warning disable CA1062 +public void MyMethod(object parameter) +{ + parameter.ToString(); // CA1062: Validate parameter is non-null +} +#pragma warning restore CA1062 + +// CORRECT - Fix the issue +public void MyMethod(object parameter) +{ + ArgumentNullException.ThrowIfNull(parameter); + parameter.ToString(); +} + +// ACCEPTABLE - SuppressMessage with valid justification (last resort only) +[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", + Justification = "TUnit framework guarantees non-null parameters from data sources")] +public async Task MyTest(IConverter converter, int expectedValue) +{ + var result = converter.GetValue(); // converter is never null from test framework + await Assert.That(result).IsEqualTo(expectedValue); +} +``` + +### Fixing StyleCop Violations + +**StyleCop warnings MUST always be fixed, never suppressed.** Common fixes: + +- **SA1202** (Static members before instance): Reorder members +- **SA1204** (Static before non-static): Move static members to top of class +- **SA1402** (One type per file): Move nested types inside parent class or split files +- **SA1611/SA1615** (Missing documentation): Add XML documentation tags +- **SA1600** (Elements should be documented): Add XML documentation to public members + +### Enforcement + +- Build will fail with `-warnaserror` flag if any analyzer warnings exist +- All pragma directives will be flagged during code review +- Use of `SuppressMessage` requires justification and review approval + ## What to Avoid - **Reflection-heavy patterns** without proper AOT suppression @@ -383,7 +493,7 @@ _total = this.WhenAnyValue( ## Important Notes - **Repository Location:** Working directory is `C:\source\reactiveui\src` -- **Main Solution:** `ReactiveUI.sln` +- **Main Solution:** `reactiveui.slnx` - **Benchmarks:** Separate solution at `Benchmarks/ReactiveUI.Benchmarks.sln` - **Integration Tests:** Platform-specific solutions in `integrationtests/` (not required for most development) - **No shallow clones:** Repository requires full recursive clone for git version information used by build system diff --git a/src/.claude/settings.local.json b/src/.claude/settings.local.json deleted file mode 100644 index 9b9d047ef3..0000000000 --- a/src/.claude/settings.local.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "permissions": { - "allow": [ - "WebFetch(domain:github.com)", - "WebSearch", - "Bash(dotnet build:*)", - "Bash(dotnet test:*)", - "Bash(ls:*)", - "Bash(grep:*)", - "Bash(dotnet clean:*)" - ] - } -} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7332944d46..464783b8d1 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -37,7 +37,7 @@ true - $(MSBuildProjectName.Contains('.Test')) + $(MSBuildProjectName.Contains('.Tests')) @@ -66,9 +66,9 @@ net8.0;net9.0;net10.0 net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0;net10.0-windows10.0.19041.0 - + $(ReactiveUIMauiTestTargets);net9.0;net10.0 - + $(ReactiveUIMauiWindowsTargets) $(ReactiveMauiTargets);net9.0;net10.0;$(ReactiveUIAndroidTargets) $(ReactiveMauiTargets);$(ReactiveUIAppleTargets) @@ -102,9 +102,8 @@ - - testconfig.json + $(AssemblyName).testconfig.json PreserveNewest @@ -130,7 +129,6 @@ - diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index fd3a50d51d..f125390d1c 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -7,9 +7,6 @@ false - - $(DefineConstants);NETSTANDARD;PORTABLE - $(DefineConstants);NET_461;XAML @@ -27,6 +24,6 @@ - + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index ed3c3649f2..7ef229833a 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -24,7 +24,7 @@ - + @@ -46,7 +46,7 @@ - + diff --git a/src/ReactiveUI.AndroidX/Builder/AndroidXReactiveUIBuilderExtensions.cs b/src/ReactiveUI.AndroidX/Builder/AndroidXReactiveUIBuilderExtensions.cs index 70a3da00da..888d25e65c 100644 --- a/src/ReactiveUI.AndroidX/Builder/AndroidXReactiveUIBuilderExtensions.cs +++ b/src/ReactiveUI.AndroidX/Builder/AndroidXReactiveUIBuilderExtensions.cs @@ -25,10 +25,6 @@ public static class AndroidXReactiveUIBuilderExtensions /// /// The builder instance. /// The builder instance for chaining. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Uses reflection to create instances of types.")] - [RequiresDynamicCode("Uses reflection to create instances of types.")] -#endif public static IReactiveUIBuilder WithAndroidX(this IReactiveUIBuilder builder) { if (builder is null) diff --git a/src/ReactiveUI.AndroidX/ControlFetcherMixin.cs b/src/ReactiveUI.AndroidX/ControlFetcherMixin.cs index 22d0c34709..d8fc49dd3b 100644 --- a/src/ReactiveUI.AndroidX/ControlFetcherMixin.cs +++ b/src/ReactiveUI.AndroidX/ControlFetcherMixin.cs @@ -25,10 +25,8 @@ public static class ControlFetcherMixin /// The fragment. /// The inflated view. /// The resolve members. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WireUpControls uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("WireUpControls uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Android resource discovery uses reflection over generated resource types that may be trimmed.")] + [RequiresDynamicCode("Android resource discovery uses reflection that may require dynamic code generation.")] public static void WireUpControls(this Fragment fragment, View inflatedView, ResolveStrategy resolveMembers = ResolveStrategy.Implicit) { ArgumentExceptionHelper.ThrowIfNull(fragment); diff --git a/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs index ea33d25aa8..6e1e835e1c 100644 --- a/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs +++ b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs @@ -15,10 +15,6 @@ namespace ReactiveUI.AndroidX; /// This is an Activity that is both an Activity and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveAppCompatActivity inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveAppCompatActivity inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveAppCompatActivity : AppCompatActivity, IReactiveObject, IReactiveNotifyPropertyChanged, IHandleObservableErrors { private readonly Subject _activated = new(); diff --git a/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity{TViewModel}.cs index 2d60c87ff5..79e0ff1cb6 100644 --- a/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity{TViewModel}.cs +++ b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity{TViewModel}.cs @@ -10,10 +10,6 @@ namespace ReactiveUI.AndroidX; /// (i.e. you can call RaiseAndSetIfChanged). /// /// The view model type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveAppCompatActivity uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveAppCompatActivity uses methods that may require unreferenced code")] -#endif public class ReactiveAppCompatActivity : ReactiveAppCompatActivity, IViewFor, ICanActivate where TViewModel : class { diff --git a/src/ReactiveUI.AndroidX/ReactiveDialogFragment.cs b/src/ReactiveUI.AndroidX/ReactiveDialogFragment.cs index 68c5db6d1e..7369e4a3c1 100644 --- a/src/ReactiveUI.AndroidX/ReactiveDialogFragment.cs +++ b/src/ReactiveUI.AndroidX/ReactiveDialogFragment.cs @@ -9,10 +9,6 @@ namespace ReactiveUI.AndroidX; /// This is a Fragment that is both an Activity and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveDialogFragment uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveDialogFragment uses methods that may require unreferenced code")] -#endif public class ReactiveDialogFragment : global::AndroidX.Fragment.App.DialogFragment, IReactiveNotifyPropertyChanged, IReactiveObject, IHandleObservableErrors { private readonly Subject _activated = new(); @@ -57,10 +53,6 @@ protected ReactiveDialogFragment() void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args); /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SuppressChangeNotifications uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SuppressChangeNotifications uses methods that may require unreferenced code")] -#endif public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this); /// diff --git a/src/ReactiveUI.AndroidX/ReactiveDialogFragment{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactiveDialogFragment{TViewModel}.cs index 459ee69deb..cec0a91d52 100644 --- a/src/ReactiveUI.AndroidX/ReactiveDialogFragment{TViewModel}.cs +++ b/src/ReactiveUI.AndroidX/ReactiveDialogFragment{TViewModel}.cs @@ -10,10 +10,6 @@ namespace ReactiveUI.AndroidX; /// (i.e. you can call RaiseAndSetIfChanged). /// /// The view model type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveDialogFragment uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveDialogFragment uses methods that may require unreferenced code")] -#endif public class ReactiveDialogFragment : ReactiveDialogFragment, IViewFor, ICanActivate where TViewModel : class { diff --git a/src/ReactiveUI.AndroidX/ReactiveFragment.cs b/src/ReactiveUI.AndroidX/ReactiveFragment.cs index 37384402c0..d0bd94455b 100644 --- a/src/ReactiveUI.AndroidX/ReactiveFragment.cs +++ b/src/ReactiveUI.AndroidX/ReactiveFragment.cs @@ -9,10 +9,6 @@ namespace ReactiveUI.AndroidX; /// This is a Fragment that is both an Activity and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveFragment inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveFragment inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif [ExcludeFromCodeCoverage] public class ReactiveFragment : global::AndroidX.Fragment.App.Fragment, IReactiveNotifyPropertyChanged, IReactiveObject, IHandleObservableErrors { diff --git a/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs index fbdec77011..909a5a939a 100644 --- a/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs +++ b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs @@ -14,10 +14,6 @@ namespace ReactiveUI.AndroidX; /// This is an Activity that is both an Activity and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveFragmentActivity inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveFragmentActivity inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveFragmentActivity : FragmentActivity, IReactiveObject, IReactiveNotifyPropertyChanged, IHandleObservableErrors { private readonly Subject _activated = new(); diff --git a/src/ReactiveUI.AndroidX/ReactiveFragmentActivity{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity{TViewModel}.cs index baf4a02c8b..85c8609875 100644 --- a/src/ReactiveUI.AndroidX/ReactiveFragmentActivity{TViewModel}.cs +++ b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity{TViewModel}.cs @@ -10,10 +10,6 @@ namespace ReactiveUI.AndroidX; /// (i.e. you can call RaiseAndSetIfChanged). /// /// The view model type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveFragmentActivity inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveFragmentActivity inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveFragmentActivity : ReactiveFragmentActivity, IViewFor, ICanActivate where TViewModel : class { diff --git a/src/ReactiveUI.AndroidX/ReactiveFragment{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactiveFragment{TViewModel}.cs index dd83c18d45..9400a30eeb 100644 --- a/src/ReactiveUI.AndroidX/ReactiveFragment{TViewModel}.cs +++ b/src/ReactiveUI.AndroidX/ReactiveFragment{TViewModel}.cs @@ -10,10 +10,6 @@ namespace ReactiveUI.AndroidX; /// (i.e. you can call RaiseAndSetIfChanged). /// /// The view model type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveFragment uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveFragment uses methods that may require unreferenced code")] -#endif public class ReactiveFragment : ReactiveFragment, IViewFor, ICanActivate where TViewModel : class { diff --git a/src/ReactiveUI.AndroidX/ReactivePreferenceFragment.cs b/src/ReactiveUI.AndroidX/ReactivePreferenceFragment.cs index ad02b3a4df..db101a1dc2 100644 --- a/src/ReactiveUI.AndroidX/ReactivePreferenceFragment.cs +++ b/src/ReactiveUI.AndroidX/ReactivePreferenceFragment.cs @@ -13,10 +13,6 @@ namespace ReactiveUI.AndroidX; /// This is a PreferenceFragment that is both an Activity and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("SuppressChangeNotifications uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("SuppressChangeNotifications uses methods that may require unreferenced code")] -#endif public abstract class ReactivePreferenceFragment : PreferenceFragmentCompat, IReactiveNotifyPropertyChanged, IReactiveObject, IHandleObservableErrors { private readonly Subject _activated = new(); @@ -65,10 +61,6 @@ protected ReactivePreferenceFragment(in IntPtr handle, JniHandleOwnership owners public IObservable Deactivated => _deactivated.AsObservable(); /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SuppressChangeNotifications uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SuppressChangeNotifications uses methods that may require unreferenced code")] -#endif public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this); /// diff --git a/src/ReactiveUI.AndroidX/ReactivePreferenceFragment{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactivePreferenceFragment{TViewModel}.cs index 96a0daa623..02bfda82e8 100644 --- a/src/ReactiveUI.AndroidX/ReactivePreferenceFragment{TViewModel}.cs +++ b/src/ReactiveUI.AndroidX/ReactivePreferenceFragment{TViewModel}.cs @@ -12,10 +12,6 @@ namespace ReactiveUI.AndroidX; /// (i.e. you can call RaiseAndSetIfChanged). /// /// The view model type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactivePreferenceFragment uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactivePreferenceFragment uses methods that may require unreferenced code")] -#endif public abstract class ReactivePreferenceFragment : ReactivePreferenceFragment, IViewFor, ICanActivate where TViewModel : class { @@ -33,10 +29,6 @@ protected ReactivePreferenceFragment() /// /// The handle. /// The ownership. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactivePreferenceFragment uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReactivePreferenceFragment uses methods that may require unreferenced code")] -#endif protected ReactivePreferenceFragment(in IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership) { diff --git a/src/ReactiveUI.AndroidX/ReactiveRecyclerViewViewHolder.cs b/src/ReactiveUI.AndroidX/ReactiveRecyclerViewViewHolder.cs index e95a790787..e48d700d07 100644 --- a/src/ReactiveUI.AndroidX/ReactiveRecyclerViewViewHolder.cs +++ b/src/ReactiveUI.AndroidX/ReactiveRecyclerViewViewHolder.cs @@ -17,10 +17,8 @@ namespace ReactiveUI.AndroidX; /// A implementation that binds to a reactive view model. /// /// The type of the view model. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveRecyclerViewViewHolder inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveRecyclerViewViewHolder inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif +[RequiresUnreferencedCode("Android property discovery uses reflection over generated resource types that may be trimmed.")] +[RequiresDynamicCode("Android property discovery discovery uses reflection that may require dynamic code generation.")] public class ReactiveRecyclerViewViewHolder : RecyclerView.ViewHolder, ILayoutViewHost, IViewFor, IReactiveNotifyPropertyChanged>, IReactiveObject, ICanActivate where TViewModel : class, IReactiveObject { diff --git a/src/ReactiveUI.AndroidX/Registrations.cs b/src/ReactiveUI.AndroidX/Registrations.cs index 6597048483..72e08dd09f 100644 --- a/src/ReactiveUI.AndroidX/Registrations.cs +++ b/src/ReactiveUI.AndroidX/Registrations.cs @@ -14,17 +14,13 @@ namespace ReactiveUI.AndroidX; public class Registrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Uses reflection to create instances of types.")] - [RequiresDynamicCode("Uses reflection to create instances of types.")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); // Leverage core Android platform registrations already present in ReactiveUI.Platforms android. // This ensures IPlatformOperations, binding converters, and schedulers are configured. - new PlatformRegistrations().Register(registerFunction); + new PlatformRegistrations().Register(registrar); // AndroidX specific registrations could be added here if needed in the future. diff --git a/src/ReactiveUI.Blazor/Builder/BlazorReactiveUIBuilderExtensions.cs b/src/ReactiveUI.Blazor/Builder/BlazorReactiveUIBuilderExtensions.cs index b855569a25..976d08c7a6 100644 --- a/src/ReactiveUI.Blazor/Builder/BlazorReactiveUIBuilderExtensions.cs +++ b/src/ReactiveUI.Blazor/Builder/BlazorReactiveUIBuilderExtensions.cs @@ -31,10 +31,6 @@ public static class BlazorReactiveUIBuilderExtensions /// /// The builder instance. /// The builder instance for chaining. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WithBlazor uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("WithBlazor uses methods that may require unreferenced code")] -#endif public static IReactiveUIBuilder WithBlazor(this IReactiveUIBuilder builder) { if (builder is null) diff --git a/src/ReactiveUI.Blazor/Internal/ReactiveComponentHelpers.cs b/src/ReactiveUI.Blazor/Internal/ReactiveComponentHelpers.cs new file mode 100644 index 0000000000..bd4f245526 --- /dev/null +++ b/src/ReactiveUI.Blazor/Internal/ReactiveComponentHelpers.cs @@ -0,0 +1,270 @@ +// Copyright (c) 2025 .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 Microsoft.AspNetCore.Components; + +namespace ReactiveUI.Blazor.Internal; + +/// +/// Internal helper methods for reactive Blazor components. +/// Provides shared functionality for activation wiring, property change observation, and state management. +/// +/// +/// +/// This class centralizes common reactive patterns used across all Blazor component base classes, +/// eliminating code duplication and providing a single source of truth for reactive behavior. +/// +/// +/// Performance: All methods are optimized to minimize allocations and use static delegates where possible +/// to avoid closure allocations. Observable creation patterns are designed for efficient subscription management. +/// +/// +internal static class ReactiveComponentHelpers +{ + /// + /// Creates an observable that produces a value for each + /// notification raised by . + /// + /// The source object that implements . + /// + /// An observable sequence of pulses, one for each property change notification. + /// + /// + /// + /// This method uses Observable.Create to create a highly efficient event-based observable + /// with direct event handler management, avoiding the overhead of Observable.FromEvent. + /// + /// + /// Performance: Observable.Create is more efficient than Observable.FromEvent as it avoids + /// delegate conversions and provides direct control over subscription lifecycle. The event + /// handler is a local function that captures minimal state, optimizing allocation overhead. + /// + /// + /// Thrown when is . + public static IObservable CreatePropertyChangedPulse(INotifyPropertyChanged source) + { + ArgumentNullException.ThrowIfNull(source); + + return Observable.Create(observer => + { + void Handler(object? sender, PropertyChangedEventArgs e) => observer.OnNext(Unit.Default); + + source.PropertyChanged += Handler; + return Disposable.Create(() => source.PropertyChanged -= Handler); + }); + } + + /// + /// Wires ReactiveUI activation semantics to the specified view model if it implements . + /// + /// The view model type that implements . + /// The view model to wire activation for. + /// The reactive component state that provides activation/deactivation observables. + /// + /// + /// This method sets up a two-way binding between the component's activation lifecycle and the view model's + /// . When the component is activated, the view model's activator is triggered. + /// When the component is deactivated, the view model's activator is deactivated. + /// + /// + /// The activation subscription is added to to ensure + /// it is disposed when the component is disposed. The deactivation subscription does not require explicit disposal + /// as it is a fire-and-forget operation that completes when the component is disposed. + /// + /// + /// Performance: This is a low-frequency setup operation that occurs once during component initialization. + /// The guard check ensures no work is done if the view model doesn't support activation. + /// + /// + /// + /// Thrown when or is . + /// + public static void WireActivationIfSupported(T? viewModel, ReactiveComponentState state) + where T : class, INotifyPropertyChanged + { + ArgumentNullException.ThrowIfNull(state); + + if (viewModel is not IActivatableViewModel avm) + { + return; + } + + // Subscribe to component activation and trigger view model activation + state.Activated + .Subscribe(_ => avm.Activator.Activate()) + .DisposeWith(state.LifetimeDisposables); + + // Deactivation subscription does not need disposal tracking beyond component lifetime + state.Deactivated.Subscribe(_ => avm.Activator.Deactivate()); + } + + /// + /// Creates an observable that emits the current view model (if non-null) and then emits each + /// subsequent non-null view model assignment. + /// + /// The view model type that implements . + /// A function that returns the current view model value. + /// + /// An action that adds a handler to the event. + /// + /// + /// An action that removes a handler from the event. + /// + /// + /// The name of the view model property to observe. Typically "ViewModel". + /// + /// + /// An observable sequence of non-null view models. Emits the current view model once (if non-null), + /// then emits each subsequent non-null view model assignment. + /// + /// + /// + /// This method creates a cold observable using Observable.Create. Each subscription + /// gets its own event handler that is properly cleaned up when the subscription is disposed. + /// + /// + /// The observable filters property changes to only emit when the view model property changes (using + /// ordinal string comparison for performance). Null view models are filtered out to ensure downstream + /// operators always receive non-null values. + /// + /// + /// Performance: Uses for property name comparison, which is + /// the fastest string comparison method and matches the typical behavior of expressions. + /// + /// + /// + /// Thrown when , , + /// , or is . + /// + public static IObservable CreateViewModelChangedStream( + Func getCurrentViewModel, + Action addPropertyChangedHandler, + Action removePropertyChangedHandler, + string viewModelPropertyName) + where T : class, INotifyPropertyChanged + { + ArgumentNullException.ThrowIfNull(getCurrentViewModel); + ArgumentNullException.ThrowIfNull(addPropertyChangedHandler); + ArgumentNullException.ThrowIfNull(removePropertyChangedHandler); + ArgumentNullException.ThrowIfNull(viewModelPropertyName); + + return Observable.Create( + observer => + { + // Emit current value once to preserve the original "Skip(1)" behavior in consumers + var current = getCurrentViewModel(); + if (current is not null) + { + observer.OnNext(current); + } + + // Handler for subsequent changes + void Handler(object? sender, PropertyChangedEventArgs e) + { + // Use ordinal comparison for best performance; nameof() produces ordinal strings + if (!string.Equals(e.PropertyName, viewModelPropertyName, StringComparison.Ordinal)) + { + return; + } + + var vm = getCurrentViewModel(); + if (vm is not null) + { + observer.OnNext(vm); + } + } + + addPropertyChangedHandler(Handler); + return Disposable.Create(() => removePropertyChangedHandler(Handler)); + }); + } + + /// + /// Wires reactivity that triggers UI re-rendering when the view model changes or when the current + /// view model raises property changed events. + /// + /// The view model type that implements . + /// A function that returns the current view model value. + /// + /// An action that adds a handler to the event. + /// + /// + /// An action that removes a handler from the event. + /// + /// + /// The name of the view model property to observe. Typically "ViewModel". + /// + /// + /// A callback to invoke when the UI should be re-rendered. Typically + /// wrapped in . + /// + /// + /// A disposable that tears down all subscriptions when disposed. Should be assigned to + /// . + /// + /// + /// + /// This method creates two subscriptions that work together to provide comprehensive UI reactivity: + /// 1. A subscription that triggers re-render when the view model instance changes (skipping the initial value). + /// 2. A subscription that triggers re-render when any property on the current view model changes. + /// + /// + /// The view model stream is created with Publish and RefCount operators + /// to ensure the underlying observable is shared between both subscriptions, preventing duplicate event handler registrations. + /// + /// + /// The Switch operator ensures that when the view model changes, the old view model's + /// property changes are automatically unsubscribed and the new view model's property changes are subscribed. + /// + /// + /// Performance: The Publish().RefCount() pattern minimizes allocations by sharing the underlying observable. + /// The Switch operator efficiently manages subscription lifecycle to prevent memory leaks from old view models. + /// + /// + /// + /// Thrown when any parameter is . + /// + public static IDisposable WireViewModelChangeReactivity( + Func getCurrentViewModel, + Action addPropertyChangedHandler, + Action removePropertyChangedHandler, + string viewModelPropertyName, + Action stateHasChangedCallback) + where T : class, INotifyPropertyChanged + { + ArgumentNullException.ThrowIfNull(getCurrentViewModel); + ArgumentNullException.ThrowIfNull(addPropertyChangedHandler); + ArgumentNullException.ThrowIfNull(removePropertyChangedHandler); + ArgumentNullException.ThrowIfNull(viewModelPropertyName); + ArgumentNullException.ThrowIfNull(stateHasChangedCallback); + + // Create a shared stream of non-null view models: + // - Emits the current ViewModel once (if non-null) + // - Emits subsequent non-null ViewModel assignments + // The Publish().RefCount(2) pattern shares the subscription between two consumers + var viewModelChanged = CreateViewModelChangedStream( + getCurrentViewModel, + addPropertyChangedHandler, + removePropertyChangedHandler, + viewModelPropertyName) + .Publish() + .RefCount(2); + + return new CompositeDisposable + { + // Skip the initial value to avoid an immediate extra render on first render + viewModelChanged + .Skip(1) + .Subscribe(_ => stateHasChangedCallback()), + + // Re-render on any ViewModel property change + // Switch unsubscribes from the previous ViewModel automatically when it changes + viewModelChanged + .Select(static vm => CreatePropertyChangedPulse(vm)) + .Switch() + .Subscribe(_ => stateHasChangedCallback()) + }; + } +} diff --git a/src/ReactiveUI.Blazor/Internal/ReactiveComponentState.cs b/src/ReactiveUI.Blazor/Internal/ReactiveComponentState.cs new file mode 100644 index 0000000000..29d94f45a8 --- /dev/null +++ b/src/ReactiveUI.Blazor/Internal/ReactiveComponentState.cs @@ -0,0 +1,163 @@ +// Copyright (c) 2025 .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.Blazor.Internal; + +/// +/// Internal state container for reactive Blazor components. +/// Manages activation lifecycle, subscriptions, and disposal semantics. +/// +/// The view model type that implements . +/// +/// +/// This class encapsulates the common reactive infrastructure shared across all reactive Blazor component base classes, +/// eliminating code duplication and centralizing allocation patterns for better performance and maintainability. +/// +/// +/// Performance: All fields are initialized inline to minimize allocation overhead. The state instance should be +/// created once per component and reused throughout the component's lifetime. +/// +/// +internal sealed class ReactiveComponentState : IDisposable + where T : class, INotifyPropertyChanged +{ + /// + /// Signals component activation. Emits when is called. + /// + private readonly Subject _initSubject = new(); + + /// + /// Signals component deactivation. Emits when is called. + /// + /// + /// Suppressed CA2213 because this subject is used for signaling only and is disposed explicitly in . + /// + [SuppressMessage("Design", "CA2213:Disposable fields should be disposed", Justification = "Disposed explicitly in Dispose method.")] + private readonly Subject _deactivateSubject = new(); + + /// + /// Holds subscriptions tied to the component lifetime. Disposed when the component is disposed. + /// + private readonly CompositeDisposable _lifetimeDisposables = []; + + /// + /// Holds subscriptions created on first render. + /// + /// + /// This SerialDisposable avoids framework conflicts that occur when certain subscriptions are created + /// during OnInitialized rather than OnAfterRender. The subscription is replaced each time + /// is assigned. + /// + private readonly SerialDisposable _firstRenderSubscriptions = new(); + + /// + /// Indicates whether the state has been disposed. Prevents double disposal. + /// + private bool _disposed; + + /// + /// Gets an observable that emits when the component is activated. + /// + /// + /// This observable emits once during component initialization and can be used to trigger + /// reactive activation patterns for view models implementing . + /// + public IObservable Activated => _initSubject.AsObservable(); + + /// + /// Gets an observable that emits when the component is deactivated. + /// + /// + /// This observable emits during component disposal, allowing cleanup operations to execute + /// while subscriptions are still active. + /// + public IObservable Deactivated => _deactivateSubject.AsObservable(); + + /// + /// Gets the composite disposable for lifetime subscriptions. + /// + /// + /// Use this to register subscriptions that should live for the entire component lifetime. + /// All subscriptions added here will be disposed when the component is disposed. + /// + [SuppressMessage("Style", "RCS1085:Use auto-implemented property", Justification = "Explicit field backing provides clarity and follows established pattern in this class.")] + public CompositeDisposable LifetimeDisposables => _lifetimeDisposables; + + /// + /// Gets or sets the disposable for first-render-only subscriptions. + /// + /// + /// + /// This property wraps a to ensure that setting a new subscription + /// automatically disposes the previous one. Typically set once during OnAfterRender when firstRender is true. + /// + /// + /// Performance: The property is intentionally implemented with explicit getters and setters rather than + /// as an auto-property to provide controlled access to the underlying SerialDisposable's Disposable property, + /// ensuring proper disposal semantics. + /// + /// + [SuppressMessage("Style", "RCS1085:Use auto-implemented property", Justification = "Intentional wrapper for SerialDisposable.Disposable property to ensure proper disposal semantics.")] + public IDisposable? FirstRenderSubscriptions + { + get => _firstRenderSubscriptions.Disposable; + set => _firstRenderSubscriptions.Disposable = value; + } + + /// + /// Notifies observers that the component has been activated. + /// + /// + /// Call this method during component initialization (typically in OnInitialized) to signal + /// that the component is now active and ready for reactive operations. + /// + public void NotifyActivated() => _initSubject.OnNext(Unit.Default); + + /// + /// Notifies observers that the component is being deactivated. + /// + /// + /// + /// Call this method during component disposal to signal that the component is shutting down. + /// This notification occurs before subscriptions are disposed, allowing observers to perform + /// cleanup while their subscriptions are still active. + /// + /// + /// Performance: This method is typically called once during disposal and incurs minimal overhead. + /// + /// + public void NotifyDeactivated() => _deactivateSubject.OnNext(Unit.Default); + + /// + /// Disposes all managed resources held by this state container. + /// + /// + /// + /// Disposal order is critical for correct cleanup behavior: + /// 1. First-render subscriptions are disposed first (may depend on lifetime subscriptions). + /// 2. Lifetime subscriptions are disposed next (general cleanup). + /// 3. Subjects are disposed last (signal completion to any remaining observers). + /// + /// + /// This method is idempotent; calling it multiple times has no effect after the first call. + /// + /// + public void Dispose() + { + if (_disposed) + { + return; + } + + _firstRenderSubscriptions.Dispose(); + _lifetimeDisposables.Dispose(); + _initSubject.Dispose(); + _deactivateSubject.Dispose(); + + _disposed = true; + } +} diff --git a/src/ReactiveUI.Blazor/ReactiveComponentBase.cs b/src/ReactiveUI.Blazor/ReactiveComponentBase.cs index 56f6db959e..a13dc3a36d 100644 --- a/src/ReactiveUI.Blazor/ReactiveComponentBase.cs +++ b/src/ReactiveUI.Blazor/ReactiveComponentBase.cs @@ -7,23 +7,41 @@ using Microsoft.AspNetCore.Components; +using ReactiveUI.Blazor.Internal; + namespace ReactiveUI.Blazor; /// -/// A base component for handling property changes and updating the blazer view appropriately. +/// A base component for handling property changes and updating the Blazor view appropriately. /// -/// The type of view model. Must support INotifyPropertyChanged. +/// The type of view model. Must support . +/// +/// +/// This component triggers when either the view model instance changes or +/// the current view model raises . +/// +/// +/// Trimming/AOT: this type avoids expression-tree-based ReactiveUI helpers (e.g. WhenAnyValue) and uses event-based +/// observables instead. +/// +/// public class ReactiveComponentBase : ComponentBase, IViewFor, INotifyPropertyChanged, ICanActivate, IDisposable where T : class, INotifyPropertyChanged { - private readonly Subject _initSubject = new(); - [SuppressMessage("Design", "CA2213: Dispose object", Justification = "Used for deactivation.")] - private readonly Subject _deactivateSubject = new(); - private readonly CompositeDisposable _compositeDisposable = []; + /// + /// Encapsulates reactive state and lifecycle management for this component. + /// + private readonly ReactiveComponentState _state = new(); + /// + /// Backing field for . + /// private T? _viewModel; - private bool _disposedValue; // To detect redundant calls + /// + /// Indicates whether the instance has been disposed. + /// + private bool _disposed; /// public event PropertyChangedEventHandler? PropertyChanged; @@ -53,15 +71,16 @@ public T? ViewModel } /// - public IObservable Activated => _initSubject.AsObservable(); + public IObservable Activated => _state.Activated; /// - public IObservable Deactivated => _deactivateSubject.AsObservable(); + public IObservable Deactivated => _state.Deactivated; - /// + /// + /// Disposes the component and releases managed resources. + /// public void Dispose() { - // Do not change this code. Put cleanup code in Dispose(bool disposing) below. Dispose(true); GC.SuppressFinalize(this); } @@ -69,53 +88,23 @@ public void Dispose() /// protected override void OnInitialized() { - if (ViewModel is IActivatableViewModel avm) - { - Activated.Subscribe(_ => avm.Activator.Activate()).DisposeWith(_compositeDisposable); - Deactivated.Subscribe(_ => avm.Activator.Deactivate()); - } - - _initSubject.OnNext(Unit.Default); + ReactiveComponentHelpers.WireActivationIfSupported(ViewModel, _state); + _state.NotifyActivated(); base.OnInitialized(); } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("OnAfterRender uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("OnAfterRender uses methods that may require unreferenced code")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "ComponentBase is an external reference")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "ComponentBase is an external reference")] -#endif protected override void OnAfterRender(bool firstRender) { if (firstRender) { - // The following subscriptions are here because if they are done in OnInitialized, they conflict with certain JavaScript frameworks. - var viewModelChanged = - this.WhenAnyValue, T?>(nameof(ViewModel)) - .WhereNotNull() - .Publish() - .RefCount(2); - - viewModelChanged - .Skip(1) // Skip the initial value to avoid unnecessary re-render when ViewModel changes - .Subscribe(_ => InvokeAsync(StateHasChanged)) - .DisposeWith(_compositeDisposable); - - viewModelChanged - .Select(x => - Observable - .FromEvent( - eventHandler => - { - void Handler(object? sender, PropertyChangedEventArgs e) => eventHandler(Unit.Default); - return Handler; - }, - eh => x.PropertyChanged += eh, - eh => x.PropertyChanged -= eh)) - .Switch() - .Subscribe(_ => InvokeAsync(StateHasChanged)) - .DisposeWith(_compositeDisposable); + // These subscriptions are intentionally created here (not OnInitialized) due to framework interop constraints. + _state.FirstRenderSubscriptions = ReactiveComponentHelpers.WireViewModelChangeReactivity( + () => ViewModel, + h => PropertyChanged += h, + h => PropertyChanged -= h, + nameof(ViewModel), + () => InvokeAsync(StateHasChanged)); } base.OnAfterRender(firstRender); @@ -124,25 +113,30 @@ protected override void OnAfterRender(bool firstRender) /// /// Invokes the property changed event. /// - /// The name of the property. - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + /// The name of the changed property. + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); /// - /// Cleans up the managed resources of the object. + /// Releases managed resources used by the component. /// - /// If it is getting called by the Dispose() method rather than a finalizer. + /// + /// to release managed resources; to release unmanaged resources only. + /// protected virtual void Dispose(bool disposing) { - if (!_disposedValue) + if (_disposed) { - if (disposing) - { - _initSubject.Dispose(); - _compositeDisposable.Dispose(); - _deactivateSubject.OnNext(Unit.Default); - } + return; + } - _disposedValue = true; + if (disposing) + { + // Notify deactivation first so observers can perform cleanup while subscriptions are still active. + _state.NotifyDeactivated(); + _state.Dispose(); } + + _disposed = true; } } diff --git a/src/ReactiveUI.Blazor/ReactiveInjectableComponentBase.cs b/src/ReactiveUI.Blazor/ReactiveInjectableComponentBase.cs index 31db8e4315..b630bd3dbc 100644 --- a/src/ReactiveUI.Blazor/ReactiveInjectableComponentBase.cs +++ b/src/ReactiveUI.Blazor/ReactiveInjectableComponentBase.cs @@ -7,23 +7,44 @@ using Microsoft.AspNetCore.Components; +using ReactiveUI.Blazor.Internal; + namespace ReactiveUI.Blazor; /// -/// A base component for handling property changes and updating the blazer view appropriately. +/// A base component for handling property changes and updating the Blazor view appropriately. /// -/// The type of view model. Must support INotifyPropertyChanged. +/// The type of view model. Must support . +/// +/// +/// This component triggers when either the view model instance changes or +/// the current view model raises . +/// +/// +/// Trimming/AOT: this type avoids expression-tree-based ReactiveUI helpers (e.g. WhenAnyValue) and uses event-based +/// observables instead. +/// +/// +/// The is provided via DI using . +/// +/// public class ReactiveInjectableComponentBase : ComponentBase, IViewFor, INotifyPropertyChanged, ICanActivate, IDisposable where T : class, INotifyPropertyChanged { - private readonly Subject _initSubject = new(); - [SuppressMessage("Design", "CA2213: Dispose object", Justification = "Used for deactivation.")] - private readonly Subject _deactivateSubject = new(); - private readonly CompositeDisposable _compositeDisposable = []; + /// + /// Encapsulates reactive state and lifecycle management for this component. + /// + private readonly ReactiveComponentState _state = new(); + /// + /// Backing field for . + /// private T? _viewModel; - private bool _disposedValue; // To detect redundant calls + /// + /// Indicates whether the instance has been disposed. + /// + private bool _disposed; /// public event PropertyChangedEventHandler? PropertyChanged; @@ -53,15 +74,16 @@ public T? ViewModel } /// - public IObservable Activated => _initSubject.AsObservable(); + public IObservable Activated => _state.Activated; /// - public IObservable Deactivated => _deactivateSubject.AsObservable(); + public IObservable Deactivated => _state.Deactivated; - /// + /// + /// Disposes the component and releases managed resources. + /// public void Dispose() { - // Do not change this code. Put cleanup code in Dispose(bool disposing) below. Dispose(true); GC.SuppressFinalize(this); } @@ -69,55 +91,23 @@ public void Dispose() /// protected override void OnInitialized() { - if (ViewModel is IActivatableViewModel avm) - { - Activated.Subscribe(_ => avm.Activator.Activate()).DisposeWith(_compositeDisposable); - Deactivated.Subscribe(_ => avm.Activator.Deactivate()); - } - - _initSubject.OnNext(Unit.Default); - + ReactiveComponentHelpers.WireActivationIfSupported(ViewModel, _state); + _state.NotifyActivated(); base.OnInitialized(); } /// - -#if NET6_0_OR_GREATER - [RequiresDynamicCode("OnAfterRender uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("OnAfterRender uses methods that may require unreferenced code")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "ComponentBase is an external reference")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "ComponentBase is an external reference")] -#endif protected override void OnAfterRender(bool firstRender) { if (firstRender) { - // The following subscriptions are here because if they are done in OnInitialized, they conflict with certain JavaScript frameworks. - var viewModelChanged = - this.WhenAnyValue, T?>(nameof(ViewModel)) - .WhereNotNull() - .Publish() - .RefCount(2); - - viewModelChanged - .Skip(1) // Skip the initial value to avoid unnecessary re-render when ViewModel changes - .Subscribe(_ => InvokeAsync(StateHasChanged)) - .DisposeWith(_compositeDisposable); - - viewModelChanged - .Select(x => - Observable - .FromEvent( - eventHandler => - { - void Handler(object? sender, PropertyChangedEventArgs e) => eventHandler(Unit.Default); - return Handler; - }, - eh => x.PropertyChanged += eh, - eh => x.PropertyChanged -= eh)) - .Switch() - .Subscribe(_ => InvokeAsync(StateHasChanged)) - .DisposeWith(_compositeDisposable); + // These subscriptions are intentionally created here (not OnInitialized) due to framework interop constraints. + _state.FirstRenderSubscriptions = ReactiveComponentHelpers.WireViewModelChangeReactivity( + () => ViewModel, + h => PropertyChanged += h, + h => PropertyChanged -= h, + nameof(ViewModel), + () => InvokeAsync(StateHasChanged)); } base.OnAfterRender(firstRender); @@ -126,25 +116,30 @@ protected override void OnAfterRender(bool firstRender) /// /// Invokes the property changed event. /// - /// The name of the property. - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + /// The name of the changed property. + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); /// - /// Cleans up the managed resources of the object. + /// Releases managed resources used by the component. /// - /// If it is getting called by the Dispose() method rather than a finalizer. + /// + /// to release managed resources; to release unmanaged resources only. + /// protected virtual void Dispose(bool disposing) { - if (!_disposedValue) + if (_disposed) { - if (disposing) - { - _initSubject.Dispose(); - _compositeDisposable.Dispose(); - _deactivateSubject.OnNext(Unit.Default); - } + return; + } - _disposedValue = true; + if (disposing) + { + // Notify deactivation first so observers can perform cleanup while subscriptions are still active. + _state.NotifyDeactivated(); + _state.Dispose(); } + + _disposed = true; } } diff --git a/src/ReactiveUI.Blazor/ReactiveLayoutComponentBase.cs b/src/ReactiveUI.Blazor/ReactiveLayoutComponentBase.cs index 5206d4c561..af06a3696f 100644 --- a/src/ReactiveUI.Blazor/ReactiveLayoutComponentBase.cs +++ b/src/ReactiveUI.Blazor/ReactiveLayoutComponentBase.cs @@ -7,23 +7,41 @@ using Microsoft.AspNetCore.Components; +using ReactiveUI.Blazor.Internal; + namespace ReactiveUI.Blazor; /// -/// A base component for handling property changes and updating the blazer view appropriately. +/// A base component for handling property changes and updating the Blazor view appropriately. /// -/// The type of view model. Must support INotifyPropertyChanged. +/// The type of view model. Must support . +/// +/// +/// This component supports ReactiveUI activation semantics and triggers +/// when either the view model instance changes or the current view model raises . +/// +/// +/// Trimming/AOT: this type avoids expression-tree-based ReactiveUI helpers (e.g. WhenAnyValue) and uses event-based +/// observables instead. +/// +/// public class ReactiveLayoutComponentBase : LayoutComponentBase, IViewFor, INotifyPropertyChanged, ICanActivate, IDisposable where T : class, INotifyPropertyChanged { - private readonly Subject _initSubject = new(); - [SuppressMessage("Design", "CA2213: Dispose object", Justification = "Used for deactivation.")] - private readonly Subject _deactivateSubject = new(); - private readonly CompositeDisposable _compositeDisposable = []; + /// + /// Encapsulates reactive state and lifecycle management for this component. + /// + private readonly ReactiveComponentState _state = new(); + /// + /// Backing field for . + /// private T? _viewModel; - private bool _disposedValue; // To detect redundant calls + /// + /// Indicates whether the instance has been disposed. + /// + private bool _disposed; /// public event PropertyChangedEventHandler? PropertyChanged; @@ -53,15 +71,16 @@ public T? ViewModel } /// - public IObservable Activated => _initSubject.AsObservable(); + public IObservable Activated => _state.Activated; /// - public IObservable Deactivated => _deactivateSubject.AsObservable(); + public IObservable Deactivated => _state.Deactivated; - /// + /// + /// Disposes the component and releases managed resources. + /// public void Dispose() { - // Do not change this code. Put cleanup code in Dispose(bool disposing) below. Dispose(true); GC.SuppressFinalize(this); } @@ -69,81 +88,54 @@ public void Dispose() /// protected override void OnInitialized() { - if (ViewModel is IActivatableViewModel avm) - { - Activated.Subscribe(_ => avm.Activator.Activate()).DisposeWith(_compositeDisposable); - Deactivated.Subscribe(_ => avm.Activator.Deactivate()); - } - - _initSubject.OnNext(Unit.Default); + ReactiveComponentHelpers.WireActivationIfSupported(ViewModel, _state); + _state.NotifyActivated(); base.OnInitialized(); } /// -#pragma warning disable RCS1168 // Parameter name differs from base name. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("OnAfterRender uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("OnAfterRender uses methods that may require unreferenced code")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "LayoutComponentBase is an external reference")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "LayoutComponentBase is an external reference")] -#endif - protected override void OnAfterRender(bool isFirstRender) -#pragma warning restore RCS1168 // Parameter name differs from base name. + protected override void OnAfterRender(bool firstRender) { - if (isFirstRender) + if (firstRender) { - var viewModelChanged = - this.WhenAnyValue, T?>(nameof(ViewModel)) - .WhereNotNull() - .Publish() - .RefCount(2); - - viewModelChanged - .Skip(1) // Skip the initial value to avoid unnecessary re-render when ViewModel changes - .Subscribe(_ => InvokeAsync(StateHasChanged)) - .DisposeWith(_compositeDisposable); - - viewModelChanged - .Select(x => - Observable - .FromEvent( - eventHandler => - { - void Handler(object? sender, PropertyChangedEventArgs e) => eventHandler(Unit.Default); - return Handler; - }, - eh => x.PropertyChanged += eh, - eh => x.PropertyChanged -= eh)) - .Switch() - .Subscribe(_ => InvokeAsync(StateHasChanged)) - .DisposeWith(_compositeDisposable); + _state.FirstRenderSubscriptions = ReactiveComponentHelpers.WireViewModelChangeReactivity( + () => ViewModel, + h => PropertyChanged += h, + h => PropertyChanged -= h, + nameof(ViewModel), + () => InvokeAsync(StateHasChanged)); } - base.OnAfterRender(isFirstRender); + base.OnAfterRender(firstRender); } /// /// Invokes the property changed event. /// /// The name of the property. - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); /// - /// Cleans up the managed resources of the object. + /// Releases managed resources used by the component. /// - /// If it is getting called by the Dispose() method rather than a finalizer. + /// + /// to release managed resources; to release unmanaged resources only. + /// protected virtual void Dispose(bool disposing) { - if (!_disposedValue) + if (_disposed) { - if (disposing) - { - _initSubject.Dispose(); - _compositeDisposable.Dispose(); - _deactivateSubject.OnNext(Unit.Default); - } + return; + } - _disposedValue = true; + if (disposing) + { + // Notify deactivation first so observers can perform cleanup while subscriptions are still active. + _state.NotifyDeactivated(); + _state.Dispose(); } + + _disposed = true; } } diff --git a/src/ReactiveUI.Blazor/ReactiveOwningComponentBase.cs b/src/ReactiveUI.Blazor/ReactiveOwningComponentBase.cs index 3570cdc9e9..362f4e2c2b 100644 --- a/src/ReactiveUI.Blazor/ReactiveOwningComponentBase.cs +++ b/src/ReactiveUI.Blazor/ReactiveOwningComponentBase.cs @@ -2,24 +2,45 @@ // 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.Runtime.CompilerServices; using Microsoft.AspNetCore.Components; +using ReactiveUI.Blazor.Internal; + namespace ReactiveUI.Blazor; /// -/// A base component for handling property changes and updating the blazer view appropriately. +/// A base component for handling property changes and updating the Blazor view appropriately. /// -/// The type of view model. Must support INotifyPropertyChanged. +/// The type of view model. Must support . +/// +/// +/// This component triggers when either the view model instance changes or +/// the current view model raises . +/// +/// +/// Trimming/AOT: this type avoids expression-tree-based ReactiveUI helpers (e.g. WhenAnyValue) and uses event-based +/// observables instead. +/// +/// +/// This type derives from so the DI scope and owned service lifetime are +/// managed by the base class. +/// +/// public class ReactiveOwningComponentBase : OwningComponentBase, IViewFor, INotifyPropertyChanged, ICanActivate where T : class, INotifyPropertyChanged { - private readonly Subject _initSubject = new(); - [SuppressMessage("Design", "CA2213: Dispose object", Justification = "Used for deactivation.")] - private readonly Subject _deactivateSubject = new(); - private readonly CompositeDisposable _compositeDisposable = []; + /// + /// Encapsulates reactive state and lifecycle management for this component. + /// + private readonly ReactiveComponentState _state = new(); + /// + /// Backing field for . + /// private T? _viewModel; /// @@ -52,28 +73,22 @@ public T? ViewModel } /// - public IObservable Activated => _initSubject.AsObservable(); + public IObservable Activated => _state.Activated; /// - public IObservable Deactivated => _deactivateSubject.AsObservable(); + public IObservable Deactivated => _state.Deactivated; /// protected override void OnInitialized() { - if (ViewModel is IActivatableViewModel avm) - { - Activated.Subscribe(_ => avm.Activator.Activate()).DisposeWith(_compositeDisposable); - Deactivated.Subscribe(_ => avm.Activator.Deactivate()); - } - - _initSubject.OnNext(Unit.Default); + ReactiveComponentHelpers.WireActivationIfSupported(ViewModel, _state); + _state.NotifyActivated(); base.OnInitialized(); } /// #if NET6_0_OR_GREATER - [RequiresDynamicCode("OnAfterRender uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("OnAfterRender uses methods that may require unreferenced code")] + [RequiresUnreferencedCode("OnAfterRender wires reactive subscriptions that may not be trimming-safe in all environments.")] [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "ComponentBase is an external reference")] [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "ComponentBase is an external reference")] #endif @@ -81,32 +96,13 @@ protected override void OnAfterRender(bool firstRender) { if (firstRender) { - // The following subscriptions are here because if they are done in OnInitialized, they conflict with certain JavaScript frameworks. - var viewModelChanged = - this.WhenAnyValue, T?>(nameof(ViewModel)) - .WhereNotNull() - .Publish() - .RefCount(2); - - viewModelChanged - .Skip(1) // Skip the initial value to avoid unnecessary re-render when ViewModel changes - .Subscribe(_ => InvokeAsync(StateHasChanged)) - .DisposeWith(_compositeDisposable); - - viewModelChanged - .Select(x => - Observable - .FromEvent( - eventHandler => - { - void Handler(object? sender, PropertyChangedEventArgs e) => eventHandler(Unit.Default); - return Handler; - }, - eh => x.PropertyChanged += eh, - eh => x.PropertyChanged -= eh)) - .Switch() - .Subscribe(_ => InvokeAsync(StateHasChanged)) - .DisposeWith(_compositeDisposable); + // These subscriptions are intentionally created here (not OnInitialized) due to framework interop constraints. + _state.FirstRenderSubscriptions = ReactiveComponentHelpers.WireViewModelChangeReactivity( + () => ViewModel, + h => PropertyChanged += h, + h => PropertyChanged -= h, + nameof(ViewModel), + () => InvokeAsync(StateHasChanged)); } base.OnAfterRender(firstRender); @@ -124,9 +120,11 @@ protected override void Dispose(bool disposing) { if (disposing) { - _initSubject.Dispose(); - _compositeDisposable.Dispose(); - _deactivateSubject.OnNext(Unit.Default); + // Notify deactivation first so observers can perform cleanup while subscriptions are still active. + _state.NotifyDeactivated(); + _state.Dispose(); } + + base.Dispose(disposing); } } diff --git a/src/ReactiveUI.Blazor/Registrations.cs b/src/ReactiveUI.Blazor/Registrations.cs index 6b95a38a81..7161dbdb8b 100644 --- a/src/ReactiveUI.Blazor/Registrations.cs +++ b/src/ReactiveUI.Blazor/Registrations.cs @@ -14,39 +14,33 @@ namespace ReactiveUI.Blazor; public class Registrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(registerFunction); + ArgumentNullException.ThrowIfNull(registrar); #else - if (registerFunction is null) + if (registrar is null) { - throw new ArgumentNullException(nameof(registerFunction)); + throw new ArgumentNullException(nameof(registrar)); } #endif - registerFunction(static () => new StringConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new ByteToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableByteToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new ShortToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableShortToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new IntegerToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableIntegerToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new LongToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableLongToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new SingleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableSingleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new DoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableDoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new DecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableDecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new PlatformOperations(), typeof(IPlatformOperations)); + registrar.RegisterConstant(static () => new StringConverter()); + registrar.RegisterConstant(static () => new ByteToStringTypeConverter()); + registrar.RegisterConstant(static () => new NullableByteToStringTypeConverter()); + registrar.RegisterConstant(static () => new ShortToStringTypeConverter()); + registrar.RegisterConstant(static () => new NullableShortToStringTypeConverter()); + registrar.RegisterConstant(static () => new IntegerToStringTypeConverter()); + registrar.RegisterConstant(static () => new NullableIntegerToStringTypeConverter()); + registrar.RegisterConstant(static () => new LongToStringTypeConverter()); + registrar.RegisterConstant(static () => new NullableLongToStringTypeConverter()); + registrar.RegisterConstant(static () => new SingleToStringTypeConverter()); + registrar.RegisterConstant(static () => new NullableSingleToStringTypeConverter()); + registrar.RegisterConstant(static () => new DoubleToStringTypeConverter()); + registrar.RegisterConstant(static () => new NullableDoubleToStringTypeConverter()); + registrar.RegisterConstant(static () => new DecimalToStringTypeConverter()); + registrar.RegisterConstant(static () => new NullableDecimalToStringTypeConverter()); + registrar.RegisterConstant(static () => new PlatformOperations()); if (Type.GetType("Mono.Runtime") is not null) { diff --git a/src/ReactiveUI.Blend/FollowObservableStateBehavior.cs b/src/ReactiveUI.Blend/FollowObservableStateBehavior.cs index b34af233f3..04c8ff0cb4 100644 --- a/src/ReactiveUI.Blend/FollowObservableStateBehavior.cs +++ b/src/ReactiveUI.Blend/FollowObservableStateBehavior.cs @@ -3,10 +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. -#if NET6_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif - using System.Reactive.Concurrency; using System.Windows; using System.Windows.Controls; @@ -17,10 +13,6 @@ namespace ReactiveUI.Blend; /// /// Behavior that tracks the state of an observable. /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("OnStateObservableChanged uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("OnStateObservableChanged uses methods that may require unreferenced code")] -#endif public class FollowObservableStateBehavior : Behavior { /// @@ -71,10 +63,6 @@ public FrameworkElement TargetObject /// /// The sender. /// The event args. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("InternalOnStateObservableChangedForTesting uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("InternalOnStateObservableChangedForTesting uses methods that may require unreferenced code")] -#endif internal static void InternalOnStateObservableChangedForTesting(DependencyObject? sender, DependencyPropertyChangedEventArgs e) => OnStateObservableChanged(sender, e); @@ -83,10 +71,6 @@ internal static void InternalOnStateObservableChangedForTesting(DependencyObject /// /// The sender. /// The instance containing the event data. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("OnStateObservableChanged uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("OnStateObservableChanged uses methods that may require unreferenced code")] -#endif protected static void OnStateObservableChanged(DependencyObject? sender, DependencyPropertyChangedEventArgs e) { ArgumentExceptionHelper.ThrowIfNotOfType(sender); diff --git a/src/ReactiveUI.Blend/ObservableTrigger.cs b/src/ReactiveUI.Blend/ObservableTrigger.cs index 3a0eb76adf..dcf44beb0b 100644 --- a/src/ReactiveUI.Blend/ObservableTrigger.cs +++ b/src/ReactiveUI.Blend/ObservableTrigger.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.Reactive.Concurrency; using System.Windows; @@ -14,10 +13,6 @@ namespace ReactiveUI.Blend; /// /// A blend based trigger which will be activated when a IObservable triggers. /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ObservableTrigger uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ObservableTrigger uses methods that may require unreferenced code")] -#endif public class ObservableTrigger : TriggerBase { /// @@ -53,10 +48,6 @@ public IObservable Observable /// /// The sender. /// The event args. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("InternalOnObservableChangedForTesting uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("InternalOnObservableChangedForTesting uses methods that may require unreferenced code")] -#endif internal static void InternalOnObservableChangedForTesting(DependencyObject sender, DependencyPropertyChangedEventArgs e) => OnObservableChanged(sender, e); @@ -65,10 +56,6 @@ internal static void InternalOnObservableChangedForTesting(DependencyObject send /// /// The sender. /// The instance containing the event data. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("OnObservableChanged uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("OnObservableChanged uses methods that may require unreferenced code")] -#endif protected static void OnObservableChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ArgumentExceptionHelper.ThrowIfNotOfType(sender, nameof(sender)); diff --git a/src/ReactiveUI.Blend/ReactiveUI.Blend.csproj b/src/ReactiveUI.Blend/ReactiveUI.Blend.csproj index 12cc72fd8c..95d14116a5 100644 --- a/src/ReactiveUI.Blend/ReactiveUI.Blend.csproj +++ b/src/ReactiveUI.Blend/ReactiveUI.Blend.csproj @@ -10,6 +10,7 @@ + diff --git a/src/ReactiveUI.Drawing/Builder/ReactiveUIBuilderDrawingExtensions.cs b/src/ReactiveUI.Drawing/Builder/ReactiveUIBuilderDrawingExtensions.cs index e3c5e9c0ee..c5204620f8 100644 --- a/src/ReactiveUI.Drawing/Builder/ReactiveUIBuilderDrawingExtensions.cs +++ b/src/ReactiveUI.Drawing/Builder/ReactiveUIBuilderDrawingExtensions.cs @@ -17,10 +17,6 @@ public static class ReactiveUIBuilderDrawingExtensions /// /// The builder instance. /// The builder instance for method chaining. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static IReactiveUIBuilder WithDrawing(this IReactiveUIBuilder builder) { ArgumentExceptionHelper.ThrowIfNull(builder); diff --git a/src/ReactiveUI.Drawing/Registrations.cs b/src/ReactiveUI.Drawing/Registrations.cs index 0b87612a19..3c2157c46b 100644 --- a/src/ReactiveUI.Drawing/Registrations.cs +++ b/src/ReactiveUI.Drawing/Registrations.cs @@ -14,18 +14,12 @@ namespace ReactiveUI.Drawing; public class Registrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); #if NETFRAMEWORK || (NET5_0_OR_GREATER && WINDOWS) - registerFunction(static () => new PlatformBitmapLoader(), typeof(IBitmapLoader)); + registrar.RegisterConstant(static () => new PlatformBitmapLoader()); #endif } } diff --git a/src/ReactiveUI.Maui/ActivationForViewFetcher.cs b/src/ReactiveUI.Maui/ActivationForViewFetcher.cs index 0ad14d4f9a..fcd3d7638a 100644 --- a/src/ReactiveUI.Maui/ActivationForViewFetcher.cs +++ b/src/ReactiveUI.Maui/ActivationForViewFetcher.cs @@ -8,6 +8,8 @@ #if WINUI_TARGET using Microsoft.UI.Xaml; +using ReactiveUI.Maui.Internal; + using Windows.Foundation; #endif @@ -39,10 +41,6 @@ public int GetAffinityForView(Type view) => ? 10 : 0; /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] -#endif public IObservable GetActivationForView(IActivatableView view) { var activation = @@ -153,10 +151,6 @@ public IObservable GetActivationForView(IActivatableView view) return appearing.Merge(disappearing); } #else -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetActivationFor uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetActivationFor uses methods that may require unreferenced code")] -#endif private static IObservable? GetActivationFor(FrameworkElement? view) { if (view is null) @@ -186,9 +180,16 @@ public IObservable GetActivationForView(IActivatableView view) x => view.Unloaded += x, x => view.Unloaded -= x); + // Observe IsHitTestVisible property changes using DependencyProperty (AOT-safe) + var isHitTestVisible = MauiReactiveHelpers.CreatePropertyValueObservable( + view, + nameof(view.IsHitTestVisible), + FrameworkElement.IsHitTestVisibleProperty, + () => view.IsHitTestVisible); + return viewLoaded .Merge(viewUnloaded) - .Select(b => b ? view.WhenAnyValue(nameof(view.IsHitTestVisible)).SkipWhile(x => !x) : Observables.False) + .Select(b => b ? isHitTestVisible.SkipWhile(x => !x) : Observables.False) .Switch() .DistinctUntilChanged(); } diff --git a/src/ReactiveUI.Maui/AutoSuspendHelper.cs b/src/ReactiveUI.Maui/AutoSuspendHelper.cs index b422cdf358..f0f0ef2f08 100644 --- a/src/ReactiveUI.Maui/AutoSuspendHelper.cs +++ b/src/ReactiveUI.Maui/AutoSuspendHelper.cs @@ -10,7 +10,7 @@ namespace ReactiveUI.Maui; /// /// /// -/// 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. @@ -26,8 +26,8 @@ namespace ReactiveUI.Maui; /// public App() /// { /// _autoSuspendHelper = new AutoSuspendHelper(); -/// RxApp.SuspensionHost.CreateNewAppState = () => new MainState(); -/// RxApp.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(FileSystem.AppDataDirectory)); +/// RxSuspension.SuspensionHost.CreateNewAppState = () => new MainState(); +/// RxSuspension.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(FileSystem.AppDataDirectory)); /// _autoSuspendHelper.OnCreate(); /// /// InitializeComponent(); @@ -44,10 +44,6 @@ namespace ReactiveUI.Maui; /// /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("AutoSuspendHelper uses RxApp.SuspensionHost which requires dynamic code generation")] -[RequiresUnreferencedCode("AutoSuspendHelper uses RxApp.SuspensionHost which may require unreferenced code")] -#endif public partial class AutoSuspendHelper : IEnableLogger, IDisposable { private readonly Subject _onSleep = new(); @@ -66,11 +62,11 @@ public partial class AutoSuspendHelper : IEnableLogger, IDisposable /// public AutoSuspendHelper() { - RxApp.SuspensionHost.IsLaunchingNew = _onLaunchingNew; - RxApp.SuspensionHost.IsResuming = _onResume; - RxApp.SuspensionHost.IsUnpausing = _onStart; - RxApp.SuspensionHost.ShouldPersistState = _onSleep; - RxApp.SuspensionHost.ShouldInvalidateState = UntimelyDemise; + RxSuspension.SuspensionHost.IsLaunchingNew = _onLaunchingNew; + RxSuspension.SuspensionHost.IsResuming = _onResume; + RxSuspension.SuspensionHost.IsUnpausing = _onStart; + RxSuspension.SuspensionHost.ShouldPersistState = _onSleep; + RxSuspension.SuspensionHost.ShouldInvalidateState = UntimelyDemise; } /// diff --git a/src/ReactiveUI.Maui/Builder/MauiReactiveUIBuilderExtensions.cs b/src/ReactiveUI.Maui/Builder/MauiReactiveUIBuilderExtensions.cs index e155bc7be8..7945f71db1 100644 --- a/src/ReactiveUI.Maui/Builder/MauiReactiveUIBuilderExtensions.cs +++ b/src/ReactiveUI.Maui/Builder/MauiReactiveUIBuilderExtensions.cs @@ -61,10 +61,6 @@ public static partial class MauiReactiveUIBuilderExtensions /// The builder instance. /// The MAUI dispatcher to use for the main thread scheduler. /// The builder instance for chaining. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static IReactiveUIBuilder WithMaui(this IReactiveUIBuilder builder, IDispatcher? dispatcher = null) { if (builder is null) @@ -105,10 +101,6 @@ public static MauiAppBuilder UseReactiveUI(this MauiAppBuilder builder, ActionThe dispatcher. /// A The builder instance for chaining. /// builder. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static MauiAppBuilder UseReactiveUI(this MauiAppBuilder builder, IDispatcher dispatcher) { if (builder is null) diff --git a/src/ReactiveUI.Maui/Common/AutoDataTemplateBindingHook.cs b/src/ReactiveUI.Maui/Common/AutoDataTemplateBindingHook.cs index 7e719ec593..6027ac8905 100644 --- a/src/ReactiveUI.Maui/Common/AutoDataTemplateBindingHook.cs +++ b/src/ReactiveUI.Maui/Common/AutoDataTemplateBindingHook.cs @@ -34,10 +34,6 @@ public class AutoDataTemplateBindingHook : IPropertyBindingHook }); /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] -#endif public bool ExecuteHook(object? source, object target, Func[]> getCurrentViewModelProperties, Func[]> getCurrentViewProperties, BindingDirection direction) { ArgumentNullException.ThrowIfNull(getCurrentViewProperties); diff --git a/src/ReactiveUI.Maui/Common/BooleanToVisibilityTypeConverter.cs b/src/ReactiveUI.Maui/Common/BooleanToVisibilityTypeConverter.cs index 067c6cf865..48ad0569d8 100644 --- a/src/ReactiveUI.Maui/Common/BooleanToVisibilityTypeConverter.cs +++ b/src/ReactiveUI.Maui/Common/BooleanToVisibilityTypeConverter.cs @@ -3,6 +3,8 @@ // 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 WINUI_TARGET using Microsoft.UI.Xaml; #else @@ -16,56 +18,44 @@ namespace ReactiveUI; #endif /// -/// This type convert converts between Boolean and XAML Visibility - the -/// conversionHint is a BooleanToVisibilityHint. +/// Converts to . /// -public class BooleanToVisibilityTypeConverter : IBindingTypeConverter +/// +/// +/// The conversion supports a as the conversion hint parameter: +/// +/// +/// - True maps to Visible, False maps to Collapsed. +/// - Inverts the boolean before conversion (True → Collapsed, False → Visible). +/// - Use Hidden instead of Collapsed for false values (MAUI only, ignored on WinUI). +/// +/// +/// Hints can be combined using bitwise OR (e.g., BooleanToVisibilityHint.Inverse | BooleanToVisibilityHint.UseHidden). +/// +/// +public sealed class BooleanToVisibilityTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(bool) && toType == typeof(Visibility)) - { - return 10; - } - - if (fromType == typeof(Visibility) && toType == typeof(bool)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(bool from, object? conversionHint, [NotNullWhen(true)] out Visibility result) { - var hint = conversionHint is BooleanToVisibilityHint visibilityHint ? - visibilityHint : - BooleanToVisibilityHint.None; + var hint = conversionHint is BooleanToVisibilityHint visibilityHint + ? visibilityHint + : BooleanToVisibilityHint.None; - if (toType == typeof(Visibility) && from is bool fromBool) - { - var fromAsBool = (hint & BooleanToVisibilityHint.Inverse) != 0 ? !fromBool : fromBool; + var value = (hint & BooleanToVisibilityHint.Inverse) != 0 ? !from : from; #if !WINUI_TARGET - var notVisible = (hint & BooleanToVisibilityHint.UseHidden) != 0 ? Visibility.Hidden : Visibility.Collapsed; + var notVisible = (hint & BooleanToVisibilityHint.UseHidden) != 0 + ? Visibility.Hidden + : Visibility.Collapsed; #else - const Visibility notVisible = Visibility.Collapsed; + const Visibility notVisible = Visibility.Collapsed; #endif - result = fromAsBool ? Visibility.Visible : notVisible; - return true; - } - - if (from is Visibility fromAsVis) - { - result = fromAsVis == Visibility.Visible ^ (hint & BooleanToVisibilityHint.Inverse) == 0; - } - else - { - result = Visibility.Visible; - } + result = value ? Visibility.Visible : notVisible; return true; } } diff --git a/src/ReactiveUI.Maui/Common/ReactivePage.cs b/src/ReactiveUI.Maui/Common/ReactivePage.cs index 2dfda8d0d5..eb492ddc6d 100644 --- a/src/ReactiveUI.Maui/Common/ReactivePage.cs +++ b/src/ReactiveUI.Maui/Common/ReactivePage.cs @@ -79,12 +79,8 @@ namespace ReactiveUI; /// /// The type of the view model backing the view. /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactivePage uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactivePage uses methods that may require unreferenced code")] -#endif [SuppressMessage("WinRT", "CsWinRT1029:Types used in signatures should be WinRT types", Justification = "This is a netstandard2.0 library")] -public partial class ReactivePage : +public partial class ReactivePage<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : Page, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI.Maui/Common/RoutedViewHost.cs b/src/ReactiveUI.Maui/Common/RoutedViewHost.cs index 31f42c552f..2d6588ba4c 100644 --- a/src/ReactiveUI.Maui/Common/RoutedViewHost.cs +++ b/src/ReactiveUI.Maui/Common/RoutedViewHost.cs @@ -7,6 +7,7 @@ using Microsoft.UI.Xaml; using ReactiveUI; +using ReactiveUI.Maui.Internal; namespace ReactiveUI; @@ -15,6 +16,8 @@ namespace ReactiveUI; /// the View and wire up the ViewModel whenever a new ViewModel is /// navigated to. Put this control as the only control in your Window. /// +[RequiresUnreferencedCode("This class uses reflection to determine view model types at runtime through ViewLocator, which may be incompatible with trimming.")] +[RequiresDynamicCode("ViewLocator.ResolveView uses reflection which is incompatible with AOT compilation.")] public partial class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger { /// @@ -35,15 +38,12 @@ public partial class RoutedViewHost : TransitioningContentControl, IActivatableV public static readonly DependencyProperty ViewContractObservableProperty = DependencyProperty.Register("ViewContractObservable", typeof(IObservable), typeof(RoutedViewHost), new PropertyMetadata(Observable.Default)); + private readonly CompositeDisposable _subscriptions = []; private string? _viewContract; /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] -#endif public RoutedViewHost() { HorizontalContentAlignment = HorizontalAlignment.Stretch; @@ -76,20 +76,46 @@ public RoutedViewHost() .StartWith(platformGetter()) .DistinctUntilChanged(); - IRoutableViewModel? currentViewModel = null; - var vmAndContract = this.WhenAnyObservable(x => x.Router.CurrentViewModel).Do(x => currentViewModel = x).StartWith(currentViewModel).CombineLatest( - this.WhenAnyObservable(x => x.ViewContractObservable).Do(x => _viewContract = x).StartWith(ViewContract), + // Observe Router property changes using DependencyProperty (AOT-friendly) + var routerChanged = MauiReactiveHelpers.CreatePropertyValueObservable( + this, + nameof(Router), + RouterProperty, + () => Router); + + // Observe ViewContractObservable property changes using DependencyProperty (AOT-friendly) + var viewContractObservableChanged = MauiReactiveHelpers.CreatePropertyValueObservable( + this, + nameof(ViewContractObservable), + ViewContractObservableProperty, + () => ViewContractObservable); + + // Observe current view model from router + var currentViewModel = routerChanged + .Where(router => router is not null) + .SelectMany(router => router!.CurrentViewModel) + .StartWith((IRoutableViewModel?)null); + + // Flatten the ViewContractObservable observable-of-observable + var viewContract = viewContractObservableChanged + .SelectMany(x => x ?? Observable.Return(null)) + .Do(x => _viewContract = x) + .StartWith(ViewContract); + + var vmAndContract = currentViewModel.CombineLatest( + viewContract, (viewModel, contract) => (viewModel, contract)); - this.WhenActivated(d => - { - // 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 - d(vmAndContract.DistinctUntilChanged().Subscribe( + // Subscribe directly without WhenActivated + // 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 + vmAndContract + .DistinctUntilChanged() + .Subscribe( ResolveViewForViewModel, - ex => RxApp.DefaultExceptionHandler.OnNext(ex))); - }); + ex => RxState.DefaultExceptionHandler.OnNext(ex)) + .DisposeWith(_subscriptions); } /// @@ -140,10 +166,8 @@ public string? ViewContract /// public IViewLocator? ViewLocator { get; set; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ResolveViewForViewModel uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ResolveViewForViewModel uses methods that may require unreferenced code")] -#endif + [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.")] private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) x) { if (x.viewModel is null) diff --git a/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs b/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs new file mode 100644 index 0000000000..fdc50e4f07 --- /dev/null +++ b/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs @@ -0,0 +1,191 @@ +// Copyright (c) 2025 .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 WINUI_TARGET +using Microsoft.UI.Xaml; + +using ReactiveUI; +using ReactiveUI.Maui.Internal; + +namespace ReactiveUI; + +/// +/// This control hosts the View associated with a Router, and will display +/// the View and wire up the ViewModel whenever a new ViewModel is +/// navigated to. Put this control as the only control in your Window. +/// This generic version provides AOT-compatibility by using compile-time type information. +/// +/// The type of the view model. Must have a public parameterless constructor and implement IRoutableViewModel. +public partial class RoutedViewHost<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : TransitioningContentControl, IActivatableView, IEnableLogger + where TViewModel : class, IRoutableViewModel +{ + /// + /// The router dependency property. + /// + public static readonly DependencyProperty RouterProperty = + DependencyProperty.Register("Router", typeof(RoutingState), typeof(RoutedViewHost), new PropertyMetadata(null)); + + /// + /// The default content property. + /// + public static readonly DependencyProperty DefaultContentProperty = + DependencyProperty.Register("DefaultContent", typeof(object), typeof(RoutedViewHost), new PropertyMetadata(null)); + + /// + /// The view contract observable property. + /// + public static readonly DependencyProperty ViewContractObservableProperty = + DependencyProperty.Register("ViewContractObservable", typeof(IObservable), typeof(RoutedViewHost), new PropertyMetadata(Observable.Default)); + + private readonly CompositeDisposable _subscriptions = []; + private string? _viewContract; + + /// + /// Initializes a new instance of the class. + /// + public RoutedViewHost() + { + HorizontalContentAlignment = HorizontalAlignment.Stretch; + VerticalContentAlignment = VerticalAlignment.Stretch; + + var platform = AppLocator.Current.GetService(); + Func platformGetter = () => default; + + if (platform is null) + { + // NB: This used to be an error but WPF design mode can't read + // good or do other stuff good. + this.Log().Error("Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."); + } + else + { + platformGetter = () => platform.GetOrientation(); + } + + ViewContractObservable = ModeDetector.InUnitTestRunner() + ? Observable.Never + : Observable.FromEvent( + eventHandler => + { + void Handler(object sender, SizeChangedEventArgs e) => eventHandler(platformGetter()); + return Handler; + }, + x => SizeChanged += x, + x => SizeChanged -= x) + .StartWith(platformGetter()) + .DistinctUntilChanged(); + + // Observe Router property changes using DependencyProperty (AOT-friendly) + var routerChanged = MauiReactiveHelpers.CreatePropertyValueObservable( + this, + nameof(Router), + RouterProperty, + () => Router); + + // Observe ViewContractObservable property changes using DependencyProperty (AOT-friendly) + var viewContractObservableChanged = MauiReactiveHelpers.CreatePropertyValueObservable( + this, + nameof(ViewContractObservable), + ViewContractObservableProperty, + () => ViewContractObservable); + + // Observe current view model from router + var currentViewModel = routerChanged + .Where(router => router is not null) + .SelectMany(router => router!.CurrentViewModel) + .StartWith((IRoutableViewModel?)null); + + // Flatten the ViewContractObservable observable-of-observable + var viewContract = viewContractObservableChanged + .SelectMany(x => x ?? Observable.Return(null)) + .Do(x => _viewContract = x) + .StartWith(ViewContract); + + var vmAndContract = currentViewModel.CombineLatest( + viewContract, + (viewModel, contract) => (viewModel, contract)); + + // Subscribe directly without WhenActivated + // 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 + vmAndContract + .DistinctUntilChanged() + .Subscribe( + ResolveViewForViewModel, + ex => RxState.DefaultExceptionHandler.OnNext(ex)) + .DisposeWith(_subscriptions); + } + + /// + /// Gets or sets the of the view model stack. + /// + public RoutingState Router + { + get => (RoutingState)GetValue(RouterProperty); + set => SetValue(RouterProperty, value); + } + + /// + /// Gets or sets the content displayed whenever there is no page currently + /// routed. + /// + public object DefaultContent + { + get => GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } + + /// + /// Gets or sets the view contract observable. + /// + /// + /// The view contract observable. + /// + public IObservable ViewContractObservable + { + get => (IObservable)GetValue(ViewContractObservableProperty); + set => SetValue(ViewContractObservableProperty, value); + } + + /// + /// Gets or sets the view contract. + /// + public string? ViewContract + { + get => _viewContract; + set => ViewContractObservable = Observable.Return(value); + } + + /// + /// Gets or sets the view locator. + /// + /// + /// The view locator. + /// + public IViewLocator? ViewLocator { get; set; } + + /// + /// Resolves and displays the view for the given view model and contract. + /// This method uses the generic ViewLocator.ResolveView{TViewModel} which is AOT-safe. + /// + /// Tuple containing the view model and contract. + private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) x) + { + if (x.viewModel is null) + { + Content = DefaultContent; + return; + } + + var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; + + // Use the generic ResolveView method - this is AOT-safe! + var view = viewLocator.ResolveView(x.contract) ?? viewLocator.ResolveView() ?? throw new Exception($"Couldn't find view for '{typeof(TViewModel).Name}'."); + view.ViewModel = x.viewModel as TViewModel; + Content = view; + } +} +#endif diff --git a/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs b/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs index 81edeacee0..0286a7ae0e 100644 --- a/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs +++ b/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs @@ -12,6 +12,8 @@ namespace ReactiveUI; /// the ViewModel property and display it. This control is very useful /// inside a DataTemplate to display the View associated with a ViewModel. /// +[RequiresUnreferencedCode("This class uses reflection to determine view model types at runtime through ViewLocator, which may be incompatible with trimming.")] +[RequiresDynamicCode("ViewLocator.ResolveView uses reflection which is incompatible with AOT compilation.")] public partial class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger { /// @@ -38,15 +40,12 @@ public partial class ViewModelViewHost : TransitioningContentControl, IViewFor, public static readonly DependencyProperty ContractFallbackByPassProperty = DependencyProperty.Register("ContractFallbackByPass", typeof(bool), typeof(ViewModelViewHost), new PropertyMetadata(false)); + private readonly CompositeDisposable _subscriptions = []; private string? _viewContract; /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ViewModelViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ViewModelViewHost uses methods that may require unreferenced code")] -#endif public ViewModelViewHost() { var platform = AppLocator.Current.GetService(); @@ -78,19 +77,28 @@ public ViewModelViewHost() .StartWith(platformGetter()) .DistinctUntilChanged(); - var contractChanged = this.WhenAnyObservable(x => x.ViewContractObservable).Do(x => _viewContract = x).StartWith(ViewContract); - var viewModelChanged = this.WhenAnyValue(nameof(ViewModel)).StartWith(ViewModel); - var vmAndContract = contractChanged + // Observe ViewModel property changes without expression trees (AOT-friendly) + var viewModelChanged = MauiReactiveHelpers.CreatePropertyValueObservable( + this, + nameof(ViewModel), + ViewModelProperty, + () => ViewModel); + + // Combine contract observable with ViewModel changes + var vmAndContract = ViewContractObservable + .Do(x => _viewContract = x) .CombineLatest(viewModelChanged, (contract, vm) => (ViewModel: vm, Contract: contract)); - this.WhenActivated(d => - { - d(contractChanged + // Subscribe directly without WhenActivated + ViewContractObservable .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(x => _viewContract = x ?? string.Empty)); + .Subscribe(x => _viewContract = x ?? string.Empty) + .DisposeWith(_subscriptions); - d(vmAndContract.DistinctUntilChanged().Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract))); - }); + vmAndContract + .DistinctUntilChanged() + .Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract)) + .DisposeWith(_subscriptions); } /// @@ -148,10 +156,8 @@ public bool ContractFallbackByPass /// /// ViewModel. /// contract used by ViewLocator. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ViewModelViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ViewModelViewHost uses methods that may require unreferenced code")] -#endif + [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.")] protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { if (viewModel is null) diff --git a/src/ReactiveUI.Maui/Common/ViewModelViewHost{TViewModel}.cs b/src/ReactiveUI.Maui/Common/ViewModelViewHost{TViewModel}.cs new file mode 100644 index 0000000000..56e1aa282c --- /dev/null +++ b/src/ReactiveUI.Maui/Common/ViewModelViewHost{TViewModel}.cs @@ -0,0 +1,197 @@ +// Copyright (c) 2025 .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 Microsoft.UI.Xaml; + +namespace ReactiveUI; + +/// +/// This content control will automatically load the View associated with +/// the ViewModel property and display it. This control is very useful +/// inside a DataTemplate to display the View associated with a ViewModel. +/// This generic version provides AOT-compatibility by using compile-time type information. +/// +/// The type of the view model. Must have a public parameterless constructor. +public partial class ViewModelViewHost<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : TransitioningContentControl, IViewFor, IEnableLogger + where TViewModel : class +{ + /// + /// The default content dependency property. + /// + public static readonly DependencyProperty DefaultContentProperty = + DependencyProperty.Register(nameof(DefaultContent), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null)); + + /// + /// The view model dependency property. + /// + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(TViewModel), typeof(ViewModelViewHost), new PropertyMetadata(null)); + + /// + /// The view contract observable dependency property. + /// + public static readonly DependencyProperty ViewContractObservableProperty = + DependencyProperty.Register(nameof(ViewContractObservable), typeof(IObservable), typeof(ViewModelViewHost), new PropertyMetadata(Observable.Default)); + + /// + /// The ContractFallbackByPass dependency property. + /// + public static readonly DependencyProperty ContractFallbackByPassProperty = + DependencyProperty.Register("ContractFallbackByPass", typeof(bool), typeof(ViewModelViewHost), new PropertyMetadata(false)); + + private readonly CompositeDisposable _subscriptions = []; + private string? _viewContract; + + /// + /// Initializes a new instance of the class. + /// + public ViewModelViewHost() + { + var platform = AppLocator.Current.GetService(); + Func platformGetter = () => default; + + if (platform is null) + { + // NB: This used to be an error but WPF design mode can't read + // good or do other stuff good. + this.Log().Error("Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."); + } + else + { + platformGetter = () => platform.GetOrientation(); + } + + ViewContractObservable = ModeDetector.InUnitTestRunner() + ? Observable.Never + : Observable.FromEvent( + eventHandler => + { +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + void Handler(object? _, SizeChangedEventArgs __) => eventHandler(platformGetter()); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter + return Handler; + }, + x => SizeChanged += x, + x => SizeChanged -= x) + .StartWith(platformGetter()) + .DistinctUntilChanged(); + + // Observe ViewModel property changes without expression trees (AOT-friendly) + var viewModelChanged = MauiReactiveHelpers.CreatePropertyValueObservable( + this, + nameof(ViewModel), + ViewModelProperty, + () => ViewModel); + + // Combine contract observable with ViewModel changes + var vmAndContract = ViewContractObservable + .Do(x => _viewContract = x) + .CombineLatest(viewModelChanged, (contract, vm) => (ViewModel: vm, Contract: contract)); + + // Subscribe directly without WhenActivated + ViewContractObservable + .ObserveOn(RxSchedulers.MainThreadScheduler) + .Subscribe(x => _viewContract = x ?? string.Empty) + .DisposeWith(_subscriptions); + + vmAndContract + .DistinctUntilChanged() + .Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract)) + .DisposeWith(_subscriptions); + } + + /// + /// Gets or sets the view contract observable. + /// + public IObservable ViewContractObservable + { + get => (IObservable)GetValue(ViewContractObservableProperty); + set => SetValue(ViewContractObservableProperty, value); + } + + /// + /// Gets or sets the content displayed by default when no content is set. + /// + public object DefaultContent + { + get => GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } + + /// + /// Gets or sets the ViewModel to display. + /// + public TViewModel? ViewModel + { + get => (TViewModel?)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + /// + /// Gets or sets the ViewModel to display (non-generic interface implementation). + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = value as TViewModel; + } + + /// + /// Gets or sets the view contract. + /// + public string? ViewContract + { + get => _viewContract; + set => ViewContractObservable = Observable.Return(value); + } + + /// + /// Gets or sets a value indicating whether should bypass the default contract fallback behavior. + /// + public bool ContractFallbackByPass + { + get => (bool)GetValue(ContractFallbackByPassProperty); + set => SetValue(ContractFallbackByPassProperty, value); + } + + /// + /// Gets or sets the view locator. + /// + public IViewLocator? ViewLocator { get; set; } + + /// + /// resolve view for view model with respect to contract. + /// + /// ViewModel. + /// contract used by ViewLocator. + protected virtual void ResolveViewForViewModel(TViewModel? viewModel, string? contract) + { + if (viewModel is null) + { + Content = DefaultContent; + return; + } + + var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; + + // Use the generic ResolveView method - this is AOT-safe! + var viewInstance = viewLocator.ResolveView(contract); + if (viewInstance is null && !ContractFallbackByPass) + { + viewInstance = viewLocator.ResolveView(); + } + + if (viewInstance is null) + { + Content = DefaultContent; + this.Log().Warn($"The {nameof(ViewModelViewHost)} could not find a valid view for the view model of type {typeof(TViewModel)} and value {viewModel}."); + return; + } + + viewInstance.ViewModel = viewModel; + + Content = viewInstance; + } +} diff --git a/src/ReactiveUI.Maui/Common/VisibilityToBooleanTypeConverter.cs b/src/ReactiveUI.Maui/Common/VisibilityToBooleanTypeConverter.cs new file mode 100644 index 0000000000..c415698f2e --- /dev/null +++ b/src/ReactiveUI.Maui/Common/VisibilityToBooleanTypeConverter.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2025 .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 WINUI_TARGET +using Microsoft.UI.Xaml; +#else +using Microsoft.Maui; +#endif + +#if IS_MAUI +namespace ReactiveUI.Maui; +#else +namespace ReactiveUI; +#endif + +/// +/// Converts to . +/// +/// +/// +/// The conversion supports a as the conversion hint parameter: +/// +/// +/// - Visible maps to True, other values map to False. +/// - Inverts the result (Visible → False, other → True). +/// +/// +/// This converter enables two-way binding between boolean properties and visibility. +/// +/// +public sealed class VisibilityToBooleanTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(Visibility from, object? conversionHint, [NotNullWhen(true)] out bool result) + { + var hint = conversionHint is BooleanToVisibilityHint visibilityHint + ? visibilityHint + : BooleanToVisibilityHint.None; + + var isVisible = from == Visibility.Visible; + result = (hint & BooleanToVisibilityHint.Inverse) != 0 ? !isVisible : isVisible; + return true; + } +} diff --git a/src/ReactiveUI.Maui/GlobalUsings.cs b/src/ReactiveUI.Maui/GlobalUsings.cs deleted file mode 100644 index 5a788f7c2d..0000000000 --- a/src/ReactiveUI.Maui/GlobalUsings.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2025 .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. - -global using System; -global using System.Diagnostics.CodeAnalysis; -global using System.Linq; -global using System.Reactive; -global using System.Reactive.Concurrency; -global using System.Reactive.Disposables; -global using System.Reactive.Disposables.Fluent; -global using System.Reactive.Linq; -global using System.Reactive.Subjects; -global using System.Threading.Tasks; - -global using Splat; diff --git a/src/ReactiveUI.Maui/Internal/MauiReactiveHelpers.cs b/src/ReactiveUI.Maui/Internal/MauiReactiveHelpers.cs new file mode 100644 index 0000000000..b56b85886d --- /dev/null +++ b/src/ReactiveUI.Maui/Internal/MauiReactiveHelpers.cs @@ -0,0 +1,159 @@ +// Copyright (c) 2025 .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.ComponentModel; +using System.Reactive; + +#if IS_WINUI +using Microsoft.UI.Xaml; +#endif + +namespace ReactiveUI.Maui.Internal; + +/// +/// Internal helper methods for reactive operations in MAUI controls. +/// These methods provide AOT-friendly alternatives to WhenAny* patterns. +/// +internal static class MauiReactiveHelpers +{ + /// + /// Creates an observable that emits when the specified property changes on the source object. + /// Uses PropertyChanged event directly without expression trees, making it AOT-compatible. + /// + /// The object to observe. + /// The name of the property to observe (use nameof()). + /// An observable that emits Unit when the property changes. + /// + /// This method uses Observable.Create for better performance compared to Observable.FromEvent. + /// It filters PropertyChanged events to only emit when the specified property changes. + /// + public static IObservable CreatePropertyChangedPulse(INotifyPropertyChanged source, string propertyName) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(propertyName); + + return Observable.Create(observer => + { + void Handler(object? sender, PropertyChangedEventArgs e) + { + if (string.IsNullOrEmpty(e.PropertyName) || + string.Equals(e.PropertyName, propertyName, StringComparison.Ordinal)) + { + observer.OnNext(Unit.Default); + } + } + + source.PropertyChanged += Handler; + return Disposable.Create(() => source.PropertyChanged -= Handler); + }); + } + + /// + /// Creates an observable that emits the current value of a property whenever it changes. + /// Uses PropertyChanged event directly without expression trees, making it AOT-compatible. + /// + /// The type of the property value. + /// The object to observe (must implement INotifyPropertyChanged). + /// The name of the property to observe (use nameof()). + /// A function to retrieve the current property value. + /// An observable that emits the property value when it changes. + /// + /// This provides an AOT-friendly alternative to WhenAnyValue by avoiding expression trees. + /// The observable immediately emits the current value upon subscription, then emits whenever the property changes. + /// This overload works with any INotifyPropertyChanged implementation and is available for MAUI. + /// + public static IObservable CreatePropertyValueObservable( + INotifyPropertyChanged source, + string propertyName, + Func getPropertyValue) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(propertyName); + ArgumentNullException.ThrowIfNull(getPropertyValue); + + return Observable.Create(observer => + { + // Emit initial value + observer.OnNext(getPropertyValue()); + + void Handler(object? sender, PropertyChangedEventArgs e) + { + if (string.IsNullOrEmpty(e.PropertyName) || + string.Equals(e.PropertyName, propertyName, StringComparison.Ordinal)) + { + observer.OnNext(getPropertyValue()); + } + } + + source.PropertyChanged += Handler; + return Disposable.Create(() => source.PropertyChanged -= Handler); + }); + } + +#if IS_WINUI + /// + /// Creates an observable that emits the current value of a DependencyProperty whenever it changes. + /// This is a WinUI-specific overload that avoids reflection by accepting the DependencyProperty directly. + /// + /// The type of the property value. + /// The DependencyObject to observe. + /// The name of the property to observe (use nameof()). + /// The DependencyProperty to observe. + /// A function to retrieve the current property value. + /// An observable that emits the property value when it changes. + /// + /// This provides an AOT-friendly alternative to WhenAnyValue by avoiding expression trees and reflection. + /// The observable immediately emits the current value upon subscription, then emits whenever the property changes. + /// + public static IObservable CreatePropertyValueObservable( + DependencyObject source, + string propertyName, + DependencyProperty property, + Func getPropertyValue) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(propertyName); + ArgumentNullException.ThrowIfNull(property); + ArgumentNullException.ThrowIfNull(getPropertyValue); + + return Observable.Create(observer => + { + // Emit initial value + observer.OnNext(getPropertyValue()); + + // Register for property changes using the provided DependencyProperty + var token = source.RegisterPropertyChangedCallback(property, (sender, dp) => + { + observer.OnNext(getPropertyValue()); + }); + + return Disposable.Create(() => source.UnregisterPropertyChangedCallback(property, token)); + }); + } +#endif + + /// + /// Wires up activation for a view model that supports activation. + /// + /// The view model to activate. + /// Observable that signals when the view is activated. + /// Observable that signals when the view is deactivated. + /// A disposable that manages the activation subscriptions. + public static IDisposable WireActivationIfSupported( + object? viewModel, + IObservable activatedSignal, + IObservable deactivatedSignal) + { + if (viewModel is not IActivatableViewModel activatable) + { + return Disposable.Empty; + } + + var activatedSub = activatedSignal.Subscribe(_ => activatable.Activator.Activate()); + var deactivatedSub = deactivatedSignal.Subscribe(_ => activatable.Activator.Deactivate()); + + return new CompositeDisposable(activatedSub, deactivatedSub); + } +} diff --git a/src/ReactiveUI.Maui/ReactiveCarouselView.cs b/src/ReactiveUI.Maui/ReactiveCarouselView.cs index 283da108be..eca2058766 100644 --- a/src/ReactiveUI.Maui/ReactiveCarouselView.cs +++ b/src/ReactiveUI.Maui/ReactiveCarouselView.cs @@ -13,11 +13,7 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCarouselView uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCarouselView uses methods that may require unreferenced code")] -#endif -public partial class ReactiveCarouselView : CarouselView, IViewFor +public partial class ReactiveCarouselView<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : CarouselView, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveContentPage.cs b/src/ReactiveUI.Maui/ReactiveContentPage.cs index ca201557f4..9091f9ee60 100644 --- a/src/ReactiveUI.Maui/ReactiveContentPage.cs +++ b/src/ReactiveUI.Maui/ReactiveContentPage.cs @@ -13,11 +13,7 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveContentPage uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveContentPage uses methods that may require unreferenced code")] -#endif -public partial class ReactiveContentPage : ContentPage, IViewFor +public partial class ReactiveContentPage<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : ContentPage, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveContentView.cs b/src/ReactiveUI.Maui/ReactiveContentView.cs index b61ef86b86..dabf645a0d 100644 --- a/src/ReactiveUI.Maui/ReactiveContentView.cs +++ b/src/ReactiveUI.Maui/ReactiveContentView.cs @@ -13,11 +13,7 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveContentView uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveContentView uses methods that may require unreferenced code")] -#endif -public partial class ReactiveContentView : ContentView, IViewFor +public partial class ReactiveContentView<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : ContentView, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveEntryCell.cs b/src/ReactiveUI.Maui/ReactiveEntryCell.cs index 85000a137a..d1566b6e17 100644 --- a/src/ReactiveUI.Maui/ReactiveEntryCell.cs +++ b/src/ReactiveUI.Maui/ReactiveEntryCell.cs @@ -14,13 +14,9 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveEntryCell uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveEntryCell uses methods that may require unreferenced code")] -#endif [Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")] [ExcludeFromCodeCoverage] -public partial class ReactiveEntryCell : EntryCell, IViewFor +public partial class ReactiveEntryCell<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : EntryCell, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveFlyoutPage.cs b/src/ReactiveUI.Maui/ReactiveFlyoutPage.cs index 5f370fa6f9..191ea310dd 100644 --- a/src/ReactiveUI.Maui/ReactiveFlyoutPage.cs +++ b/src/ReactiveUI.Maui/ReactiveFlyoutPage.cs @@ -13,11 +13,7 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveFlyoutPage uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveFlyoutPage uses methods that may require unreferenced code")] -#endif -public partial class ReactiveFlyoutPage : FlyoutPage, IViewFor +public partial class ReactiveFlyoutPage<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : FlyoutPage, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveImageCell.cs b/src/ReactiveUI.Maui/ReactiveImageCell.cs index 096d3481ab..d89b8452b4 100644 --- a/src/ReactiveUI.Maui/ReactiveImageCell.cs +++ b/src/ReactiveUI.Maui/ReactiveImageCell.cs @@ -14,13 +14,9 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveImageCell uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveImageCell uses methods that may require unreferenced code")] -#endif [Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")] [ExcludeFromCodeCoverage] -public partial class ReactiveImageCell : ImageCell, IViewFor +public partial class ReactiveImageCell<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : ImageCell, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveImageItemView.cs b/src/ReactiveUI.Maui/ReactiveImageItemView.cs index 320758b37b..832868b266 100644 --- a/src/ReactiveUI.Maui/ReactiveImageItemView.cs +++ b/src/ReactiveUI.Maui/ReactiveImageItemView.cs @@ -15,11 +15,7 @@ namespace ReactiveUI.Maui; /// /// The type of the view model. /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveImageItemView uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveImageItemView uses methods that may require unreferenced code")] -#endif -public partial class ReactiveImageItemView : ReactiveContentView +public partial class ReactiveImageItemView<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : ReactiveContentView where TViewModel : class { /// @@ -67,6 +63,7 @@ public partial class ReactiveImageItemView : ReactiveContentView), default(Color)); + private readonly CompositeDisposable _propertyBindings = []; private readonly Image _image; private readonly Label _textLabel; private readonly Label _detailLabel; @@ -81,20 +78,23 @@ public ReactiveImageItemView() WidthRequest = 40, HeightRequest = 40, VerticalOptions = LayoutOptions.Center, - HorizontalOptions = LayoutOptions.Start + HorizontalOptions = LayoutOptions.Start, + Source = ImageSource // Set initial value }; _textLabel = new Label { FontSize = 16, - VerticalOptions = LayoutOptions.Center + VerticalOptions = LayoutOptions.Center, + Text = Text // Set initial value }; _detailLabel = new Label { FontSize = 12, VerticalOptions = LayoutOptions.Center, - Opacity = 0.7 + Opacity = 0.7, + Text = Detail // Set initial value }; var textStackLayout = new StackLayout @@ -116,12 +116,26 @@ public ReactiveImageItemView() Content = mainStackLayout; - // Bind the control properties to the bindable properties - _image.SetBinding(Image.SourceProperty, new Binding(nameof(ImageSource), source: this)); - _textLabel.SetBinding(Label.TextProperty, new Binding(nameof(Text), source: this)); - _textLabel.SetBinding(Label.TextColorProperty, new Binding(nameof(TextColor), source: this)); - _detailLabel.SetBinding(Label.TextProperty, new Binding(nameof(Detail), source: this)); - _detailLabel.SetBinding(Label.TextColorProperty, new Binding(nameof(DetailColor), source: this)); + // Use expression-based property observation instead of string-based bindings (AOT-safe) + MauiReactiveHelpers.CreatePropertyValueObservable(this, nameof(ImageSource), () => ImageSource) + .Subscribe(value => _image.Source = value) + .DisposeWith(_propertyBindings); + + MauiReactiveHelpers.CreatePropertyValueObservable(this, nameof(Text), () => Text) + .Subscribe(value => _textLabel.Text = value) + .DisposeWith(_propertyBindings); + + MauiReactiveHelpers.CreatePropertyValueObservable(this, nameof(TextColor), () => TextColor) + .Subscribe(value => _textLabel.TextColor = value) + .DisposeWith(_propertyBindings); + + MauiReactiveHelpers.CreatePropertyValueObservable(this, nameof(Detail), () => Detail) + .Subscribe(value => _detailLabel.Text = value) + .DisposeWith(_propertyBindings); + + MauiReactiveHelpers.CreatePropertyValueObservable(this, nameof(DetailColor), () => DetailColor) + .Subscribe(value => _detailLabel.TextColor = value) + .DisposeWith(_propertyBindings); } /// diff --git a/src/ReactiveUI.Maui/ReactiveMasterDetailPage.cs b/src/ReactiveUI.Maui/ReactiveMasterDetailPage.cs index fd17b2e9a6..a7c9b00cf2 100644 --- a/src/ReactiveUI.Maui/ReactiveMasterDetailPage.cs +++ b/src/ReactiveUI.Maui/ReactiveMasterDetailPage.cs @@ -13,11 +13,7 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveMasterDetailPage uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveMasterDetailPage uses methods that may require unreferenced code")] -#endif -public partial class ReactiveMasterDetailPage : FlyoutPage, IViewFor +public partial class ReactiveMasterDetailPage<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : FlyoutPage, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveMultiPage.cs b/src/ReactiveUI.Maui/ReactiveMultiPage.cs index d7f0b3b0f9..41e3a97670 100644 --- a/src/ReactiveUI.Maui/ReactiveMultiPage.cs +++ b/src/ReactiveUI.Maui/ReactiveMultiPage.cs @@ -14,12 +14,7 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveMultiPage uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveMultiPage uses methods that may require unreferenced code")] -[SuppressMessage("Trimming", "IL2091:Target generic argument does not satisfy 'DynamicallyAccessedMembersAttribute' in target method or type. The generic parameter of the source method or type does not have matching annotations.", Justification = "MultiPage is a third party component")] -#endif -public abstract class ReactiveMultiPage : MultiPage, IViewFor +public abstract class ReactiveMultiPage<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] TPage, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : MultiPage, IViewFor where TPage : Page where TViewModel : class { diff --git a/src/ReactiveUI.Maui/ReactiveNavigationPage.cs b/src/ReactiveUI.Maui/ReactiveNavigationPage.cs index 4937d5893c..e1701654bc 100644 --- a/src/ReactiveUI.Maui/ReactiveNavigationPage.cs +++ b/src/ReactiveUI.Maui/ReactiveNavigationPage.cs @@ -12,11 +12,7 @@ namespace ReactiveUI.Maui; /// /// The type of the view model. /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveNavigationPage uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveNavigationPage uses methods that may require unreferenced code")] -#endif -public partial class ReactiveNavigationPage : NavigationPage, IViewFor +public partial class ReactiveNavigationPage<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : NavigationPage, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveShell.cs b/src/ReactiveUI.Maui/ReactiveShell.cs index 6879627c46..d28a9d52f7 100644 --- a/src/ReactiveUI.Maui/ReactiveShell.cs +++ b/src/ReactiveUI.Maui/ReactiveShell.cs @@ -13,11 +13,7 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveShell uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveShell uses methods that may require unreferenced code")] -#endif -public partial class ReactiveShell : Shell, IViewFor +public partial class ReactiveShell<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : Shell, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveShellContent.cs b/src/ReactiveUI.Maui/ReactiveShellContent.cs index 294100864b..c3213d8ec9 100644 --- a/src/ReactiveUI.Maui/ReactiveShellContent.cs +++ b/src/ReactiveUI.Maui/ReactiveShellContent.cs @@ -13,11 +13,7 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveShellContent uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveShellContent uses methods that may require unreferenced code")] -#endif -public partial class ReactiveShellContent : ShellContent, IActivatableView +public partial class ReactiveShellContent<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : ShellContent, IActivatableView where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveSwitchCell.cs b/src/ReactiveUI.Maui/ReactiveSwitchCell.cs index 775aff938b..1a6dfc8979 100644 --- a/src/ReactiveUI.Maui/ReactiveSwitchCell.cs +++ b/src/ReactiveUI.Maui/ReactiveSwitchCell.cs @@ -14,13 +14,9 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveSwitchCell uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveSwitchCell uses methods that may require unreferenced code")] -#endif [Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")] [ExcludeFromCodeCoverage] -public partial class ReactiveSwitchCell : SwitchCell, IViewFor +public partial class ReactiveSwitchCell<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : SwitchCell, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveTabbedPage.cs b/src/ReactiveUI.Maui/ReactiveTabbedPage.cs index 6ef1b244ce..9c2e555845 100644 --- a/src/ReactiveUI.Maui/ReactiveTabbedPage.cs +++ b/src/ReactiveUI.Maui/ReactiveTabbedPage.cs @@ -13,11 +13,7 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTabbedPage uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTabbedPage uses methods that may require unreferenced code")] -#endif -public partial class ReactiveTabbedPage : TabbedPage, IViewFor +public partial class ReactiveTabbedPage<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : TabbedPage, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveTextCell.cs b/src/ReactiveUI.Maui/ReactiveTextCell.cs index e29ae8cc40..ad6f945d37 100644 --- a/src/ReactiveUI.Maui/ReactiveTextCell.cs +++ b/src/ReactiveUI.Maui/ReactiveTextCell.cs @@ -14,13 +14,9 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTextCell uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTextCell uses methods that may require unreferenced code")] -#endif [Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")] [ExcludeFromCodeCoverage] -public partial class ReactiveTextCell : TextCell, IViewFor +public partial class ReactiveTextCell<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : TextCell, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/ReactiveTextItemView.cs b/src/ReactiveUI.Maui/ReactiveTextItemView.cs index c1ca2479a4..c23ee8dd32 100644 --- a/src/ReactiveUI.Maui/ReactiveTextItemView.cs +++ b/src/ReactiveUI.Maui/ReactiveTextItemView.cs @@ -15,11 +15,7 @@ namespace ReactiveUI.Maui; /// /// The type of the view model. /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTextItemView uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTextItemView uses methods that may require unreferenced code")] -#endif -public partial class ReactiveTextItemView : ReactiveContentView +public partial class ReactiveTextItemView<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : ReactiveContentView where TViewModel : class { /// @@ -58,6 +54,7 @@ public partial class ReactiveTextItemView : ReactiveContentView), default(Color)); + private readonly CompositeDisposable _propertyBindings = []; private readonly Label _textLabel; private readonly Label _detailLabel; @@ -69,14 +66,16 @@ public ReactiveTextItemView() _textLabel = new Label { FontSize = 16, - VerticalOptions = LayoutOptions.Center + VerticalOptions = LayoutOptions.Center, + Text = Text // Set initial value }; _detailLabel = new Label { FontSize = 12, VerticalOptions = LayoutOptions.Center, - Opacity = 0.7 + Opacity = 0.7, + Text = Detail // Set initial value }; var stackLayout = new StackLayout @@ -89,11 +88,22 @@ public ReactiveTextItemView() Content = stackLayout; - // Bind the label properties to the bindable properties - _textLabel.SetBinding(Label.TextProperty, new Binding(nameof(Text), source: this)); - _textLabel.SetBinding(Label.TextColorProperty, new Binding(nameof(TextColor), source: this)); - _detailLabel.SetBinding(Label.TextProperty, new Binding(nameof(Detail), source: this)); - _detailLabel.SetBinding(Label.TextColorProperty, new Binding(nameof(DetailColor), source: this)); + // Use expression-based property observation instead of string-based bindings (AOT-safe) + MauiReactiveHelpers.CreatePropertyValueObservable(this, nameof(Text), () => Text) + .Subscribe(value => _textLabel.Text = value) + .DisposeWith(_propertyBindings); + + MauiReactiveHelpers.CreatePropertyValueObservable(this, nameof(TextColor), () => TextColor) + .Subscribe(value => _textLabel.TextColor = value) + .DisposeWith(_propertyBindings); + + MauiReactiveHelpers.CreatePropertyValueObservable(this, nameof(Detail), () => Detail) + .Subscribe(value => _detailLabel.Text = value) + .DisposeWith(_propertyBindings); + + MauiReactiveHelpers.CreatePropertyValueObservable(this, nameof(DetailColor), () => DetailColor) + .Subscribe(value => _detailLabel.TextColor = value) + .DisposeWith(_propertyBindings); } /// diff --git a/src/ReactiveUI.Maui/ReactiveUI.Maui.csproj b/src/ReactiveUI.Maui/ReactiveUI.Maui.csproj index e805586e08..3886fc821a 100644 --- a/src/ReactiveUI.Maui/ReactiveUI.Maui.csproj +++ b/src/ReactiveUI.Maui/ReactiveUI.Maui.csproj @@ -22,6 +22,22 @@ + + + + + + + + + + + + + + + + @@ -39,7 +55,9 @@ + + diff --git a/src/ReactiveUI.Maui/ReactiveViewCell.cs b/src/ReactiveUI.Maui/ReactiveViewCell.cs index d92cc3ed63..c21df79c40 100644 --- a/src/ReactiveUI.Maui/ReactiveViewCell.cs +++ b/src/ReactiveUI.Maui/ReactiveViewCell.cs @@ -14,13 +14,9 @@ namespace ReactiveUI.Maui; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveViewCell uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveViewCell uses methods that may require unreferenced code")] -#endif [Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")] [ExcludeFromCodeCoverage] -public partial class ReactiveViewCell : ViewCell, IViewFor +public partial class ReactiveViewCell<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : ViewCell, IViewFor where TViewModel : class { /// diff --git a/src/ReactiveUI.Maui/Registrations.cs b/src/ReactiveUI.Maui/Registrations.cs index 085b68596c..f35817b8b1 100644 --- a/src/ReactiveUI.Maui/Registrations.cs +++ b/src/ReactiveUI.Maui/Registrations.cs @@ -22,24 +22,19 @@ namespace ReactiveUI.Maui; public class Registrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentNullException.ThrowIfNull(registerFunction); + ArgumentNullException.ThrowIfNull(registrar); - registerFunction(static () => new ActivationForViewFetcher(), typeof(IActivationForViewFetcher)); - registerFunction(static () => new BooleanToVisibilityTypeConverter(), typeof(IBindingTypeConverter)); + registrar.RegisterConstant(static () => new ActivationForViewFetcher()); + registrar.RegisterConstant(static () => new BooleanToVisibilityTypeConverter()); + registrar.RegisterConstant(static () => new VisibilityToBooleanTypeConverter()); #if WINUI_TARGET - registerFunction(static () => new PlatformOperations(), typeof(IPlatformOperations)); - registerFunction(static () => new DependencyObjectObservableForProperty(), typeof(ICreatesObservableForProperty)); - registerFunction(static () => new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); - registerFunction(static () => new ComponentModelTypeConverter(), typeof(IBindingTypeConverter)); + registrar.RegisterConstant(static () => new PlatformOperations()); + registrar.RegisterConstant(static () => new DependencyObjectObservableForProperty()); + registrar.RegisterConstant(static () => new AutoDataTemplateBindingHook()); + registrar.RegisterConstant(static () => new ComponentModelFallbackConverter()); if (!ModeDetector.InUnitTestRunner()) { @@ -47,7 +42,7 @@ public void Register(Action, Type> registerFunction) RxSchedulers.TaskpoolScheduler = TaskPoolScheduler.Default; } - RxApp.SuppressViewCommandBindingMessage = true; + RxSchedulers.SuppressViewCommandBindingMessage = true; #endif } } diff --git a/src/ReactiveUI.Maui/RoutedViewHost.cs b/src/ReactiveUI.Maui/RoutedViewHost.cs index 5dd74cf5d3..2a099b595e 100644 --- a/src/ReactiveUI.Maui/RoutedViewHost.cs +++ b/src/ReactiveUI.Maui/RoutedViewHost.cs @@ -35,130 +35,138 @@ public partial class RoutedViewHost : NavigationPage, IActivatableView, IEnableL typeof(RoutedViewHost), false); + private readonly CompositeDisposable _subscriptions = []; private string? _action; + private bool _currentlyNavigating; /// /// Initializes a new instance of the class. /// /// You *must* register an IScreen class representing your App's main Screen. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("This class uses reflection to determine view model types at runtime through ViewLocator, which may be incompatible with trimming.")] + [RequiresDynamicCode("ViewLocator.ResolveView uses reflection which is incompatible with AOT compilation.")] public RoutedViewHost() { - this.WhenActivated(async disposable => - { - var currentlyNavigating = false; - - Observable.FromEventPattern( - x => Router!.NavigationStack.CollectionChanged += x, - x => Router!.NavigationStack.CollectionChanged -= x) - .Where(_ => !currentlyNavigating && Router?.NavigationStack.Count == 0) - .Subscribe(async _ => await SyncNavigationStacksAsync()) - .DisposeWith(disposable); - - Router? - .NavigateBack - .Subscribe(async _ => + // Subscribe directly without WhenActivated + Observable.FromEventPattern( + x => Router!.NavigationStack.CollectionChanged += x, + x => Router!.NavigationStack.CollectionChanged -= x) + .Where(_ => !_currentlyNavigating && Router?.NavigationStack.Count == 0) + .Subscribe(async _ => await SyncNavigationStacksAsync()) + .DisposeWith(_subscriptions); + + Router? + .NavigateBack + .Subscribe(async _ => + { + try { - try - { - currentlyNavigating = true; - await PopAsync(); - } - finally - { - currentlyNavigating = false; - } + _currentlyNavigating = true; + await PopAsync(); + } + finally + { + _currentlyNavigating = false; + } - _action = "NavigatedBack"; - InvalidateCurrentViewModel(); - await SyncNavigationStacksAsync(); - }) - .DisposeWith(disposable); - - Router? - .Navigate - .Where(_ => StacksAreDifferent()) - .ObserveOn(RxSchedulers.MainThreadScheduler) - .SelectMany(_ => PagesForViewModel(Router.GetCurrentViewModel())) - .SelectMany(async page => + _action = "NavigatedBack"; + InvalidateCurrentViewModel(); + await SyncNavigationStacksAsync(); + }) + .DisposeWith(_subscriptions); + + Router? + .Navigate + .Where(_ => StacksAreDifferent()) + .ObserveOn(RxSchedulers.MainThreadScheduler) + .SelectMany(_ => PagesForViewModel(Router.GetCurrentViewModel())) + .SelectMany(async page => + { + var animated = true; + var attribute = page.GetType().GetCustomAttribute(); + if (attribute is not null) { - var animated = true; - var attribute = page.GetType().GetCustomAttribute(); - if (attribute is not null) - { - animated = false; - } + animated = false; + } - try - { - currentlyNavigating = true; - await PushAsync(page, animated); - } - finally - { - currentlyNavigating = false; - } + try + { + _currentlyNavigating = true; + await PushAsync(page, animated); + } + finally + { + _currentlyNavigating = false; + } - await SyncNavigationStacksAsync(); - - return page; - }) - .Subscribe() - .DisposeWith(disposable); - - var poppingEvent = Observable.FromEvent, Unit>( - eventHandler => - { - void Handler(object? sender, NavigationEventArgs e) => eventHandler(Unit.Default); - return Handler; - }, - x => Popped += x, - x => Popped -= x); - - // NB: User pressed the Application back as opposed to requesting Back via Router.NavigateBack. - poppingEvent - .Where(_ => !currentlyNavigating && Router is not null) - .Subscribe(_ => + await SyncNavigationStacksAsync(); + + return page; + }) + .Subscribe() + .DisposeWith(_subscriptions); + + var poppingEvent = Observable.FromEvent, Unit>( + eventHandler => + { + void Handler(object? sender, NavigationEventArgs e) => eventHandler(Unit.Default); + return Handler; + }, + x => Popped += x, + x => Popped -= x); + + // NB: User pressed the Application back as opposed to requesting Back via Router.NavigateBack. + poppingEvent + .Where(_ => !_currentlyNavigating && Router is not null) + .Subscribe(_ => + { + if (Router?.NavigationStack.Count > 0) { - if (Router?.NavigationStack.Count > 0) - { - Router.NavigationStack.RemoveAt(Router.NavigationStack.Count - 1); - } + Router.NavigationStack.RemoveAt(Router.NavigationStack.Count - 1); + } - _action = "Popped"; - InvalidateCurrentViewModel(); - }) - .DisposeWith(disposable); - - var poppingToRootEvent = Observable.FromEvent, Unit>( - eventHandler => - { - void Handler(object? sender, NavigationEventArgs e) => eventHandler(Unit.Default); - return Handler; - }, - x => PoppedToRoot += x, - x => PoppedToRoot -= x); - - poppingToRootEvent - .Where(_ => !currentlyNavigating && Router is not null) - .Subscribe(_ => + _action = "Popped"; + InvalidateCurrentViewModel(); + }) + .DisposeWith(_subscriptions); + + var poppingToRootEvent = Observable.FromEvent, Unit>( + eventHandler => + { + void Handler(object? sender, NavigationEventArgs e) => eventHandler(Unit.Default); + return Handler; + }, + x => PoppedToRoot += x, + x => PoppedToRoot -= x); + + poppingToRootEvent + .Where(_ => !_currentlyNavigating && Router is not null) + .Subscribe(_ => + { + for (var i = Router?.NavigationStack.Count - 1; i > 0; i--) { - for (var i = Router?.NavigationStack.Count - 1; i > 0; i--) + if (i.HasValue) { - if (i.HasValue) - { - Router?.NavigationStack.RemoveAt(i.Value); - } + Router?.NavigationStack.RemoveAt(i.Value); } + } - _action = "PoppedToRoot"; - InvalidateCurrentViewModel(); - }) - .DisposeWith(disposable); - await SyncNavigationStacksAsync(); + _action = "PoppedToRoot"; + InvalidateCurrentViewModel(); + }) + .DisposeWith(_subscriptions); + + // Perform initial sync asynchronously + _ = Task.Run(async () => + { + try + { + await SyncNavigationStacksAsync(); + } + catch (Exception ex) + { + this.Log().Error(ex, "Failed to perform initial navigation stack sync"); + } }); var screen = AppLocator.Current.GetService() ?? throw new Exception("You *must* register an IScreen class representing your App's main Screen"); @@ -188,10 +196,8 @@ public bool SetTitleOnNavigate /// /// The vm. /// An observable of the page associated to a . -#if NET6_0_OR_GREATER - [RequiresDynamicCode("PagesForViewModel uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("PagesForViewModel uses methods that may require unreferenced code")] -#endif + [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.")] protected virtual IObservable PagesForViewModel(IRoutableViewModel? vm) { if (vm is null) @@ -223,10 +229,8 @@ protected virtual IObservable PagesForViewModel(IRoutableViewModel? vm) /// /// The vm. /// An observable of the page associated to a . -#if NET6_0_OR_GREATER - [RequiresDynamicCode("PagesForViewModel uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("PagesForViewModel uses methods that may require unreferenced code")] -#endif + [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.")] protected virtual Page PageForViewModel(IRoutableViewModel vm) { ArgumentNullException.ThrowIfNull(vm); @@ -276,10 +280,8 @@ protected void InvalidateCurrentViewModel() /// to affect manipulations like Add or Clear. /// /// A representing the asynchronous operation. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SyncNavigationStacksAsync uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SyncNavigationStacksAsync uses methods that may require unreferenced code")] -#endif + [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.")] protected async Task SyncNavigationStacksAsync() { if (Navigation.NavigationStack.Count != Router.NavigationStack.Count diff --git a/src/ReactiveUI.Maui/RoutedViewHost{TViewModel}.cs b/src/ReactiveUI.Maui/RoutedViewHost{TViewModel}.cs new file mode 100644 index 0000000000..c2c3592d24 --- /dev/null +++ b/src/ReactiveUI.Maui/RoutedViewHost{TViewModel}.cs @@ -0,0 +1,332 @@ +// Copyright (c) 2025 .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.Specialized; +using System.Reflection; + +using Microsoft.Maui.Controls; + +namespace ReactiveUI.Maui; + +/// +/// This is a generic that serves as a router with compile-time type safety. +/// This version is fully AOT-compatible and does not use reflection-based view resolution. +/// +/// The type of the view model. Must have a public parameterless constructor. +/// +/// +public partial class RoutedViewHost<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : NavigationPage, IActivatableView, IEnableLogger + where TViewModel : class, IRoutableViewModel +{ + /// + /// The router bindable property. + /// + public static readonly BindableProperty RouterProperty = BindableProperty.Create( + nameof(Router), + typeof(RoutingState), + typeof(RoutedViewHost), + default(RoutingState)); + + /// + /// The Set Title on Navigate property. + /// + public static readonly BindableProperty SetTitleOnNavigateProperty = BindableProperty.Create( + nameof(SetTitleOnNavigate), + typeof(bool), + typeof(RoutedViewHost), + false); + + private readonly CompositeDisposable _subscriptions = []; + private string? _action; + private bool _currentlyNavigating; + + /// + /// Initializes a new instance of the class. + /// + /// You *must* register an IScreen class representing your App's main Screen. + public RoutedViewHost() + { + // Subscribe directly without WhenActivated + Observable.FromEventPattern( + x => Router!.NavigationStack.CollectionChanged += x, + x => Router!.NavigationStack.CollectionChanged -= x) + .Where(_ => !_currentlyNavigating && Router?.NavigationStack.Count == 0) + .Subscribe(async _ => await SyncNavigationStacksAsync()) + .DisposeWith(_subscriptions); + + Router? + .NavigateBack + .Subscribe(async _ => + { + try + { + _currentlyNavigating = true; + await PopAsync(); + } + finally + { + _currentlyNavigating = false; + } + + _action = "NavigatedBack"; + InvalidateCurrentViewModel(); + await SyncNavigationStacksAsync(); + }) + .DisposeWith(_subscriptions); + + Router? + .Navigate + .Where(_ => StacksAreDifferent()) + .ObserveOn(RxSchedulers.MainThreadScheduler) + .SelectMany(_ => PagesForViewModel(Router.GetCurrentViewModel())) + .SelectMany(async page => + { + var animated = true; + var attribute = page.GetType().GetCustomAttribute(); + if (attribute is not null) + { + animated = false; + } + + try + { + _currentlyNavigating = true; + await PushAsync(page, animated); + } + finally + { + _currentlyNavigating = false; + } + + await SyncNavigationStacksAsync(); + + return page; + }) + .Subscribe() + .DisposeWith(_subscriptions); + + var poppingEvent = Observable.FromEvent, Unit>( + eventHandler => + { + void Handler(object? sender, NavigationEventArgs e) => eventHandler(Unit.Default); + return Handler; + }, + x => Popped += x, + x => Popped -= x); + + // NB: User pressed the Application back as opposed to requesting Back via Router.NavigateBack. + poppingEvent + .Where(_ => !_currentlyNavigating && Router is not null) + .Subscribe(_ => + { + if (Router?.NavigationStack.Count > 0) + { + Router.NavigationStack.RemoveAt(Router.NavigationStack.Count - 1); + } + + _action = "Popped"; + InvalidateCurrentViewModel(); + }) + .DisposeWith(_subscriptions); + + var poppingToRootEvent = Observable.FromEvent, Unit>( + eventHandler => + { + void Handler(object? sender, NavigationEventArgs e) => eventHandler(Unit.Default); + return Handler; + }, + x => PoppedToRoot += x, + x => PoppedToRoot -= x); + + poppingToRootEvent + .Where(_ => !_currentlyNavigating && Router is not null) + .Subscribe(_ => + { + for (var i = Router?.NavigationStack.Count - 1; i > 0; i--) + { + if (i.HasValue) + { + Router?.NavigationStack.RemoveAt(i.Value); + } + } + + _action = "PoppedToRoot"; + InvalidateCurrentViewModel(); + }) + .DisposeWith(_subscriptions); + + // Perform initial sync asynchronously + _ = Task.Run(async () => + { + try + { + await SyncNavigationStacksAsync(); + } + catch (Exception ex) + { + this.Log().Error(ex, "Failed to perform initial navigation stack sync"); + } + }); + + var screen = AppLocator.Current.GetService() ?? throw new Exception("You *must* register an IScreen class representing your App's main Screen"); + Router = screen.Router; + } + + /// + /// Gets or sets the of the view model stack. + /// + public RoutingState Router + { + get => (RoutingState)GetValue(RouterProperty); + set => SetValue(RouterProperty, value); + } + + /// + /// Gets or sets a value indicating whether gets or sets the Set Title of the view model stack. + /// + public bool SetTitleOnNavigate + { + get => (bool)GetValue(SetTitleOnNavigateProperty); + set => SetValue(SetTitleOnNavigateProperty, value); + } + + /// + /// Pages for view model. + /// + /// The vm. + /// An observable of the page associated to a . + protected virtual IObservable PagesForViewModel(IRoutableViewModel? vm) + { + if (vm is null) + { + return Observable.Empty(); + } + + // Use the generic ResolveView method - this is AOT-safe! + 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}>"; + + return Observable.Throw(new Exception(msg)); + } + + ret.ViewModel = vm as TViewModel; + + var pg = (Page)ret; + if (SetTitleOnNavigate) + { + pg.Title = vm.UrlPathSegment; + } + + return Observable.Return(pg); + } + + /// + /// Page for view model. + /// + /// The vm. + /// A page associated to a . + protected virtual Page PageForViewModel(IRoutableViewModel vm) + { + ArgumentNullException.ThrowIfNull(vm); + + // Use the generic ResolveView method - this is AOT-safe! + 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}>"; + + throw new Exception(msg); + } + + ret.ViewModel = vm as TViewModel; + + var pg = (Page)ret; + + if (SetTitleOnNavigate) + { + RxSchedulers.MainThreadScheduler.Schedule(() => pg.Title = vm.UrlPathSegment); + } + + return pg; + } + + /// + /// Invalidates current page view model. + /// + protected void InvalidateCurrentViewModel() + { + var vm = Router?.GetCurrentViewModel(); + if (CurrentPage is IViewFor page && vm is not null) + { + if (page.ViewModel?.GetType() == vm.GetType()) + { + // don't replace view model if vm is null or an incompatible type. + page.ViewModel = vm; + } + else + { + this.Log().Info($"The view type '{page.GetType().FullName}' is not compatible with '{vm.GetType().FullName}' this was called by {_action}, the viewmodel was not invalidated"); + } + } + } + + /// + /// Syncs page's navigation stack with + /// to affect manipulations like Add or Clear. + /// + /// A representing the asynchronous operation. + protected async Task SyncNavigationStacksAsync() + { + if (Navigation.NavigationStack.Count != Router.NavigationStack.Count + || StacksAreDifferent()) + { + if (Navigation.NavigationStack.Count > 2) + { + for (var i = Navigation.NavigationStack.Count - 2; i >= 0; i--) + { + Navigation.RemovePage(Navigation.NavigationStack[i]); + } + } + + Page? rootPage; + if (Navigation.NavigationStack.Count >= 1) + { + rootPage = Navigation.NavigationStack[0]; + } + else + { + rootPage = PageForViewModel(Router.NavigationStack[0]); + await Navigation.PushAsync(rootPage, false); + } + + if (Router.NavigationStack.Count >= 1) + { + for (var i = 0; i < Router.NavigationStack.Count - 1; i++) + { + var page = PageForViewModel(Router.NavigationStack[i]); + Navigation.InsertPageBefore(page, rootPage); + } + } + } + } + + private bool StacksAreDifferent() + { + for (var i = 0; i < Router.NavigationStack.Count; i++) + { + var vm = Router.NavigationStack[i]; + var page = Navigation.NavigationStack[i]; + + if (page is not IViewFor view || !ReferenceEquals(view.ViewModel, vm)) + { + return true; + } + } + + return false; + } +} diff --git a/src/ReactiveUI.Maui/ViewModelViewHost.cs b/src/ReactiveUI.Maui/ViewModelViewHost.cs index bc1e7b8d37..27ac208721 100644 --- a/src/ReactiveUI.Maui/ViewModelViewHost.cs +++ b/src/ReactiveUI.Maui/ViewModelViewHost.cs @@ -12,6 +12,8 @@ namespace ReactiveUI.Maui; /// to be displayed should be assigned to the property. Optionally, the chosen view can be /// customized by specifying a contract via or . /// +[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 partial class ViewModelViewHost : ContentView, IViewFor { /// @@ -49,15 +51,12 @@ public partial class ViewModelViewHost : ContentView, IViewFor typeof(ViewModelViewHost), false); + private readonly CompositeDisposable _subscriptions = []; private string? _viewContract; /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ViewModelViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ViewModelViewHost uses methods that may require unreferenced code")] -#endif public ViewModelViewHost() { // NB: InUnitTestRunner also returns true in Design Mode @@ -69,19 +68,25 @@ public ViewModelViewHost() ViewContractObservable = Observable.Default; - var vmAndContract = this.WhenAnyValue(nameof(ViewModel)).CombineLatest( - this.WhenAnyObservable(x => x.ViewContractObservable), - (vm, contract) => new { ViewModel = vm, Contract = contract, }); - - this.WhenActivated(() => - (IDisposable[])[ - vmAndContract.Subscribe(x => - { - _viewContract = x.Contract; - - ResolveViewForViewModel(x.ViewModel, x.Contract); - }) - ]); + // Observe ViewModel property changes without expression trees (AOT-friendly) + var viewModelChanged = MauiReactiveHelpers.CreatePropertyValueObservable( + this, + nameof(ViewModel), + () => ViewModel); + + // Combine ViewModel and ViewContractObservable streams + var vmAndContract = viewModelChanged.CombineLatest( + ViewContractObservable, + (vm, contract) => new { ViewModel = vm, Contract = contract }); + + // Subscribe directly without WhenActivated + vmAndContract + .Subscribe(x => + { + _viewContract = x.Contract; + ResolveViewForViewModel(x.ViewModel, x.Contract); + }) + .DisposeWith(_subscriptions); } /// @@ -142,10 +147,8 @@ public bool ContractFallbackByPass /// /// ViewModel. /// contract used by ViewLocator. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ResolveViewForViewModel uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ResolveViewForViewModel uses methods that may require unreferenced code")] -#endif + [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.")] protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { if (viewModel is null) @@ -164,12 +167,12 @@ protected virtual void ResolveViewForViewModel(object? viewModel, string? contra if (viewInstance is null) { - throw new Exception($"Couldn't find view for '{viewModel}'."); + throw new InvalidOperationException($"Couldn't find view for '{viewModel}'."); } if (viewInstance is not View castView) { - throw new Exception($"View '{viewInstance.GetType().FullName}' is not a subclass of '{typeof(View).FullName}'."); + throw new InvalidOperationException($"View '{viewInstance.GetType().FullName}' is not a subclass of '{typeof(View).FullName}'."); } viewInstance.ViewModel = viewModel; diff --git a/src/ReactiveUI.Maui/ViewModelViewHost{TViewModel}.cs b/src/ReactiveUI.Maui/ViewModelViewHost{TViewModel}.cs new file mode 100644 index 0000000000..9d20336ae7 --- /dev/null +++ b/src/ReactiveUI.Maui/ViewModelViewHost{TViewModel}.cs @@ -0,0 +1,195 @@ +// Copyright (c) 2025 .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 Microsoft.Maui.Controls; + +namespace ReactiveUI.Maui; + +/// +/// This content view will automatically load and host the view for the given view model. The view model whose view is +/// to be displayed should be assigned to the property. Optionally, the chosen view can be +/// customized by specifying a contract via or . +/// +/// The type of the view model. Must have a public parameterless constructor for AOT compatibility. +/// +/// This is the AOT-compatible generic version of ViewModelViewHost. It uses compile-time type information +/// to resolve views without reflection, making it safe for Native AOT and trimming scenarios. +/// +public partial class ViewModelViewHost<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel> : ContentView, IViewFor + where TViewModel : class +{ + /// + /// Identifies the property. + /// + public static readonly BindableProperty ViewModelProperty = BindableProperty.Create( + nameof(ViewModel), + typeof(TViewModel), + typeof(ViewModelViewHost)); + + /// + /// Identifies the property. + /// + public static readonly BindableProperty DefaultContentProperty = BindableProperty.Create( + nameof(DefaultContent), + typeof(View), + typeof(ViewModelViewHost), + default(View)); + + /// + /// Identifies the property. + /// + public static readonly BindableProperty ViewContractObservableProperty = BindableProperty.Create( + nameof(ViewContractObservable), + typeof(IObservable), + typeof(ViewModelViewHost), + Observable.Never); + + /// + /// The ContractFallbackByPass dependency property. + /// + public static readonly BindableProperty ContractFallbackByPassProperty = BindableProperty.Create( + nameof(ContractFallbackByPass), + typeof(bool), + typeof(ViewModelViewHost), + false); + + private readonly CompositeDisposable _subscriptions = []; + private string? _viewContract; + + /// + /// Initializes a new instance of the class. + /// + public ViewModelViewHost() + { + // NB: InUnitTestRunner also returns true in Design Mode + if (ModeDetector.InUnitTestRunner()) + { + ViewContractObservable = Observable.Never; + return; + } + + ViewContractObservable = Observable.Default; + + // Observe ViewModel property changes without expression trees (AOT-friendly) + var viewModelChanged = MauiReactiveHelpers.CreatePropertyValueObservable( + this, + nameof(ViewModel), + () => ViewModel); + + // Combine ViewModel and ViewContractObservable streams + var vmAndContract = viewModelChanged.CombineLatest( + ViewContractObservable, + (vm, contract) => new { ViewModel = vm, Contract = contract }); + + // Subscribe directly without WhenActivated + vmAndContract + .Subscribe(x => + { + _viewContract = x.Contract; + ResolveViewForViewModel(x.ViewModel, x.Contract); + }) + .DisposeWith(_subscriptions); + } + + /// + /// Gets or sets the view model whose associated view is to be displayed. + /// + public TViewModel? ViewModel + { + get => (TViewModel?)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + /// + /// Gets or sets the view model whose associated view is to be displayed. + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TViewModel?)value; + } + + /// + /// Gets or sets the content to display when is . + /// + public View DefaultContent + { + get => (View)GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } + + /// + /// Gets or sets the observable which signals when the contract to use when resolving the view for the given view model has changed. + /// + public IObservable ViewContractObservable + { + get => (IObservable)GetValue(ViewContractObservableProperty); + set => SetValue(ViewContractObservableProperty, value); + } + + /// + /// Gets or sets the fixed contract to use when resolving the view for the given view model. + /// + /// + /// This property is a mere convenience so that a fixed contract can be assigned directly in XAML. + /// + public string? ViewContract + { + get => _viewContract; + set => ViewContractObservable = Observable.Return(value); + } + + /// + /// Gets or sets a value indicating whether should bypass the default contract fallback behavior. + /// + public bool ContractFallbackByPass + { + get => (bool)GetValue(ContractFallbackByPassProperty); + set => SetValue(ContractFallbackByPassProperty, value); + } + + /// + /// Gets or sets the override for the view locator to use when resolving the view. If unspecified, will be used. + /// + public IViewLocator? ViewLocator { get; set; } + + /// + /// Resolves and displays the view for the given view model with respect to the contract. + /// This method uses the generic ResolveView method which is AOT-compatible. + /// + /// The view model to resolve a view for. + /// The contract to use when resolving the view. + protected virtual void ResolveViewForViewModel(TViewModel? viewModel, string? contract) + { + if (viewModel is null) + { + Content = DefaultContent; + return; + } + + var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; + + // Use the generic ResolveView method - this is AOT-safe! + var viewInstance = viewLocator.ResolveView(contract); + if (viewInstance is null && !ContractFallbackByPass) + { + viewInstance = viewLocator.ResolveView(); + } + + if (viewInstance is null) + { + throw new InvalidOperationException($"Couldn't find view for '{viewModel}'."); + } + + if (viewInstance is not View castView) + { + throw new InvalidOperationException($"View '{viewInstance.GetType().FullName}' is not a subclass of '{typeof(View).FullName}'."); + } + + viewInstance.ViewModel = viewModel; + + Content = castView; + } +} diff --git a/src/ReactiveUI.Maui/WinUI/DependencyObjectObservableForProperty.cs b/src/ReactiveUI.Maui/WinUI/DependencyObjectObservableForProperty.cs index c2d480819a..09c3f88670 100644 --- a/src/ReactiveUI.Maui/WinUI/DependencyObjectObservableForProperty.cs +++ b/src/ReactiveUI.Maui/WinUI/DependencyObjectObservableForProperty.cs @@ -6,9 +6,10 @@ #if WINUI_TARGET using System.Diagnostics.CodeAnalysis; using System.Globalization; + #if IS_MAUI -using System.Linq.Expressions; #endif +using System.Linq.Expressions; using System.Reflection; using Microsoft.UI.Xaml; @@ -21,10 +22,7 @@ namespace ReactiveUI; public class DependencyObjectObservableForProperty : ICreatesObservableForProperty { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) { if (!typeof(DependencyObject).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) @@ -41,10 +39,7 @@ public int GetAffinityForObject(Type type, string propertyName, bool beforeChang } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] [RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] -#endif public IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { ArgumentNullException.ThrowIfNull(sender); @@ -92,10 +87,7 @@ public int GetAffinityForObject(Type type, string propertyName, bool beforeChang }); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ActuallyGetProperty uses methods that require dynamic code generation")] [RequiresUnreferencedCode("ActuallyGetProperty uses methods that may require unreferenced code")] -#endif private static PropertyInfo? ActuallyGetProperty(TypeInfo typeInfo, string propertyName) { var current = typeInfo; @@ -113,10 +105,7 @@ public int GetAffinityForObject(Type type, string propertyName, bool beforeChang return null; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ActuallyGetField uses methods that require dynamic code generation")] [RequiresUnreferencedCode("ActuallyGetField uses methods that may require unreferenced code")] -#endif private static FieldInfo? ActuallyGetField(TypeInfo typeInfo, string propertyName) { var current = typeInfo; @@ -134,10 +123,7 @@ public int GetAffinityForObject(Type type, string propertyName, bool beforeChang return null; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetDependencyPropertyFetcher uses methods that require dynamic code generation")] [RequiresUnreferencedCode("GetDependencyPropertyFetcher uses methods that may require unreferenced code")] -#endif private static Func? GetDependencyPropertyFetcher(Type type, string propertyName) { var typeInfo = type.GetTypeInfo(); diff --git a/src/ReactiveUI.Maui/WinUI/DispatcherQueueScheduler.cs b/src/ReactiveUI.Maui/WinUI/DispatcherQueueScheduler.cs index be1cebfe4d..efcea406f0 100644 --- a/src/ReactiveUI.Maui/WinUI/DispatcherQueueScheduler.cs +++ b/src/ReactiveUI.Maui/WinUI/DispatcherQueueScheduler.cs @@ -129,7 +129,7 @@ private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func { - var t = Interlocked.Exchange(ref timer, null); + var t = System.Threading.Interlocked.Exchange(ref timer, null); if (t != null) { try @@ -149,7 +149,7 @@ private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func { - var t = Interlocked.Exchange(ref timer, null); + var t = System.Threading.Interlocked.Exchange(ref timer, null); if (t != null) { t.Stop(); @@ -196,7 +196,7 @@ public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func< return Disposable.Create(() => { - var t = Interlocked.Exchange(ref timer, null); + var t = System.Threading.Interlocked.Exchange(ref timer, null); if (t != null) { t.Stop(); diff --git a/src/ReactiveUI.Testing.Reactive/ReactiveUI.Testing.Reactive.csproj b/src/ReactiveUI.Testing.Reactive/ReactiveUI.Testing.Reactive.csproj new file mode 100644 index 0000000000..92e783a4c3 --- /dev/null +++ b/src/ReactiveUI.Testing.Reactive/ReactiveUI.Testing.Reactive.csproj @@ -0,0 +1,21 @@ + + + $(ReactiveUIModernTargets) + ReactiveUI.Testing.Reactive + ReactiveUI.Testing.Reactive + false + Provides TestScheduler extensions for testing ReactiveUI applications with Microsoft.Reactive.Testing + ReactiveUI.Testing.Reactive + mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;frp;test;testscheduler; + + + + + + + + + + + + diff --git a/src/ReactiveUI.Testing.Reactive/TestSchedulerExtensions.cs b/src/ReactiveUI.Testing.Reactive/TestSchedulerExtensions.cs new file mode 100644 index 0000000000..9fe3c81ee5 --- /dev/null +++ b/src/ReactiveUI.Testing.Reactive/TestSchedulerExtensions.cs @@ -0,0 +1,100 @@ +// Copyright (c) 2025 .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.Reactive; +using Microsoft.Reactive.Testing; + +namespace ReactiveUI.Testing.Reactive; + +/// +/// Extension methods for TestScheduler from Microsoft.Reactive.Testing. +/// +public static class TestSchedulerExtensions +{ + /// + /// AdvanceToMs moves the TestScheduler to the specified time in + /// milliseconds. + /// + /// The scheduler to advance. + /// The time offset to set the TestScheduler + /// to, in milliseconds. Note that this is *not* additive or + /// incremental, it sets the time. + public static void AdvanceToMs(this TestScheduler scheduler, double milliseconds) + { + ArgumentExceptionHelper.ThrowIfNull(scheduler); + + scheduler.AdvanceTo(scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds))); + } + + /// + /// AdvanceByMs moves the TestScheduler along by the specified time in + /// milliseconds. + /// + /// The scheduler to advance. + /// The relative time to advance the TestScheduler + /// by, in milliseconds. + public static void AdvanceByMs(this TestScheduler scheduler, double milliseconds) + { + ArgumentExceptionHelper.ThrowIfNull(scheduler); + + scheduler.AdvanceBy(scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds))); + } + + /// + /// OnNextAt is a method to help create simulated input Observables in + /// conjunction with CreateHotObservable or CreateColdObservable. + /// + /// The type. + /// The scheduler to fire from. + /// The time offset to fire the notification + /// on the recorded notification. + /// The value to produce. + /// A recorded notification that can be provided to + /// TestScheduler.CreateHotObservable. + public static Recorded> OnNextAt(this TestScheduler scheduler, double milliseconds, T value) => + new( + scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds)), + Notification.CreateOnNext(value)); + + /// + /// OnErrorAt is a method to help create simulated input Observables in + /// conjunction with CreateHotObservable or CreateColdObservable. + /// + /// The type. + /// The scheduler to fire from. + /// The time offset to fire the notification + /// on the recorded notification. + /// The exception to terminate the Observable + /// with. + /// A recorded notification that can be provided to + /// TestScheduler.CreateHotObservable. + public static Recorded> OnErrorAt(this TestScheduler scheduler, double milliseconds, Exception ex) => + new( + scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds)), + Notification.CreateOnError(ex)); + + /// + /// OnCompletedAt is a method to help create simulated input Observables in + /// conjunction with CreateHotObservable or CreateColdObservable. + /// + /// The type. + /// The scheduler to fire from. + /// The time offset to fire the notification + /// on the recorded notification. + /// A recorded notification that can be provided to + /// TestScheduler.CreateHotObservable. + public static Recorded> OnCompletedAt(this TestScheduler scheduler, double milliseconds) => + new( + scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds)), + Notification.CreateOnCompleted()); + + /// + /// Converts a timespan to a virtual time for testing. + /// + /// The scheduler. + /// Timespan to convert. + /// Timespan for virtual scheduler to use. + public static long FromTimeSpan(this TestScheduler scheduler, TimeSpan span) => span.Ticks; +} diff --git a/src/ReactiveUI.Testing/ReactiveUI.Testing.csproj b/src/ReactiveUI.Testing/ReactiveUI.Testing.csproj index 10a446781e..a71d5427e5 100644 --- a/src/ReactiveUI.Testing/ReactiveUI.Testing.csproj +++ b/src/ReactiveUI.Testing/ReactiveUI.Testing.csproj @@ -20,7 +20,6 @@ - diff --git a/src/ReactiveUI.Testing/SchedulerExtensions.cs b/src/ReactiveUI.Testing/SchedulerExtensions.cs index 7d24eec139..0bfc6c3640 100644 --- a/src/ReactiveUI.Testing/SchedulerExtensions.cs +++ b/src/ReactiveUI.Testing/SchedulerExtensions.cs @@ -4,7 +4,6 @@ // See the LICENSE file in the project root for full license information. using System.Threading; -using Microsoft.Reactive.Testing; namespace ReactiveUI.Testing; @@ -22,22 +21,18 @@ public static class SchedulerExtensions /// The scheduler to use. /// An object that when disposed, restores the previous default /// schedulers. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WithScheduler uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("WithScheduler uses methods that may require unreferenced code")] -#endif public static IDisposable WithScheduler(IScheduler scheduler) { - var prevDef = RxApp.MainThreadScheduler; - var prevTask = RxApp.TaskpoolScheduler; + var prevDef = RxSchedulers.MainThreadScheduler; + var prevTask = RxSchedulers.TaskpoolScheduler; - RxApp.MainThreadScheduler = scheduler; - RxApp.TaskpoolScheduler = scheduler; + RxSchedulers.MainThreadScheduler = scheduler; + RxSchedulers.TaskpoolScheduler = scheduler; return Disposable.Create(() => { - RxApp.MainThreadScheduler = prevDef; - RxApp.TaskpoolScheduler = prevTask; + RxSchedulers.MainThreadScheduler = prevDef; + RxSchedulers.TaskpoolScheduler = prevTask; }); } @@ -52,10 +47,6 @@ public static IDisposable WithScheduler(IScheduler scheduler) /// The scheduler to use. /// The function to execute. /// The return value of the function. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("With uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("With uses methods that may require unreferenced code")] -#endif public static TRet With(this T scheduler, Func block) where T : IScheduler { @@ -81,10 +72,6 @@ public static TRet With(this T scheduler, Func block) /// The scheduler to use. /// The function to execute. /// The return value of the function. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WithAsync uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("WithAsync uses methods that may require unreferenced code")] -#endif public static async Task WithAsync(this T scheduler, Func> block) where T : IScheduler { @@ -106,10 +93,6 @@ public static async Task WithAsync(this T scheduler, FuncThe type. /// The scheduler to use. /// The action to execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("With uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("With uses methods that may require unreferenced code")] -#endif public static void With(this T scheduler, Action block) where T : IScheduler => scheduler.With(x => @@ -126,10 +109,6 @@ public static void With(this T scheduler, Action block) /// The scheduler to use. /// The action to execute. /// A representing the asynchronous operation. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WithAsync uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("WithAsync uses methods that may require unreferenced code")] -#endif public static Task WithAsync(this T scheduler, Func block) where T : IScheduler => scheduler.WithAsync(async x => @@ -137,91 +116,4 @@ public static Task WithAsync(this T scheduler, Func block) await block(x).ConfigureAwait(false); return 0; }); - - /// - /// AdvanceToMs moves the TestScheduler to the specified time in - /// milliseconds. - /// - /// The scheduler to advance. - /// The time offset to set the TestScheduler - /// to, in milliseconds. Note that this is *not* additive or - /// incremental, it sets the time. - public static void AdvanceToMs(this TestScheduler scheduler, double milliseconds) - { - ArgumentExceptionHelper.ThrowIfNull(scheduler); - - scheduler.AdvanceTo(scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds))); - } - - /// - /// AdvanceByMs moves the TestScheduler along by the specified time in - /// milliseconds. - /// - /// The scheduler to advance. - /// The relative time to advance the TestScheduler - /// by, in milliseconds. - public static void AdvanceByMs(this TestScheduler scheduler, double milliseconds) - { - ArgumentExceptionHelper.ThrowIfNull(scheduler); - - scheduler.AdvanceBy(scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds))); - } - - /// - /// OnNextAt is a method to help create simulated input Observables in - /// conjunction with CreateHotObservable or CreateColdObservable. - /// - /// The type. - /// The scheduler to fire from. - /// The time offset to fire the notification - /// on the recorded notification. - /// The value to produce. - /// A recorded notification that can be provided to - /// TestScheduler.CreateHotObservable. - public static Recorded> OnNextAt(this TestScheduler scheduler, double milliseconds, T value) => - new( - scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds)), - Notification.CreateOnNext(value)); - - /// - /// OnErrorAt is a method to help create simulated input Observables in - /// conjunction with CreateHotObservable or CreateColdObservable. - /// - /// The type. - /// The scheduler to fire from. - /// The time offset to fire the notification - /// on the recorded notification. - /// The exception to terminate the Observable - /// with. - /// A recorded notification that can be provided to - /// TestScheduler.CreateHotObservable. - public static Recorded> OnErrorAt(this TestScheduler scheduler, double milliseconds, Exception ex) => - new( - scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds)), - Notification.CreateOnError(ex)); - - /// - /// OnCompletedAt is a method to help create simulated input Observables in - /// conjunction with CreateHotObservable or CreateColdObservable. - /// - /// The type. - /// The scheduler to fire from. - /// The time offset to fire the notification - /// on the recorded notification. - /// A recorded notification that can be provided to - /// TestScheduler.CreateHotObservable. - public static Recorded> OnCompletedAt(this TestScheduler scheduler, double milliseconds) => - new( - scheduler.FromTimeSpan(TimeSpan.FromMilliseconds(milliseconds)), - Notification.CreateOnCompleted()); - - /// - /// Converts a timespan to a virtual time for testing. - /// - /// The scheduler. - /// Timespan to convert. - /// Timespan for virtual scheduler to use. - public static long FromTimeSpan(this TestScheduler scheduler, TimeSpan span) => span.Ticks; } - -// vim: tw=120 ts=4 sw=4 et : diff --git a/src/ReactiveUI.WinUI/Builder/WinUIReactiveUIBuilderExtensions.cs b/src/ReactiveUI.WinUI/Builder/WinUIReactiveUIBuilderExtensions.cs index 00457bee91..8b4a34c89e 100644 --- a/src/ReactiveUI.WinUI/Builder/WinUIReactiveUIBuilderExtensions.cs +++ b/src/ReactiveUI.WinUI/Builder/WinUIReactiveUIBuilderExtensions.cs @@ -23,10 +23,6 @@ public static class WinUIReactiveUIBuilderExtensions /// /// The builder instance. /// The builder instance for chaining. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WinUIReactiveUIBuilderExtensions uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("WinUIReactiveUIBuilderExtensions uses methods that may require unreferenced code")] -#endif public static IReactiveUIBuilder WithWinUI(this IReactiveUIBuilder builder) { ArgumentExceptionHelper.ThrowIfNull(builder); diff --git a/src/ReactiveUI.WinUI/GlobalUsings.cs b/src/ReactiveUI.WinUI/GlobalUsings.cs deleted file mode 100644 index 59ac6b836d..0000000000 --- a/src/ReactiveUI.WinUI/GlobalUsings.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2025 .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. - -global using System; -global using System.Diagnostics.CodeAnalysis; -global using System.Linq; -global using System.Linq.Expressions; -global using System.Reactive.Concurrency; -global using System.Reactive.Disposables; -global using System.Reactive.Linq; -global using System.Threading; - -global using Splat; diff --git a/src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj b/src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj index a37f527bb3..fff7797a01 100644 --- a/src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj +++ b/src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj @@ -27,12 +27,29 @@ + + + + + + + + + + + + + + + + + diff --git a/src/ReactiveUI.Winforms/ActivationForViewFetcher.cs b/src/ReactiveUI.Winforms/ActivationForViewFetcher.cs index a2d548179d..b46f047081 100644 --- a/src/ReactiveUI.Winforms/ActivationForViewFetcher.cs +++ b/src/ReactiveUI.Winforms/ActivationForViewFetcher.cs @@ -21,10 +21,6 @@ public class ActivationForViewFetcher : IActivationForViewFetcher, IEnableLogger public int GetAffinityForView(Type view) => typeof(Control).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0; /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] -#endif public IObservable GetActivationForView(IActivatableView view) { // Startup: Control.HandleCreated > Control.BindingContextChanged > Form.Load > Control.VisibleChanged > Form.Activated > Form.Shown diff --git a/src/ReactiveUI.Winforms/ContentControlBindingHook.cs b/src/ReactiveUI.Winforms/ContentControlBindingHook.cs index 575c25de03..0af41829c8 100644 --- a/src/ReactiveUI.Winforms/ContentControlBindingHook.cs +++ b/src/ReactiveUI.Winforms/ContentControlBindingHook.cs @@ -13,10 +13,6 @@ namespace ReactiveUI.Winforms; public class ContentControlBindingHook : IPropertyBindingHook { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] -#endif public bool ExecuteHook(object? source, object target, Func[]> getCurrentViewModelProperties, Func[]> getCurrentViewProperties, BindingDirection direction) { ArgumentExceptionHelper.ThrowIfNull(getCurrentViewProperties); diff --git a/src/ReactiveUI.Winforms/CreatesWinformsCommandBinding.cs b/src/ReactiveUI.Winforms/CreatesWinformsCommandBinding.cs index 645841dc37..b3dad7252b 100644 --- a/src/ReactiveUI.Winforms/CreatesWinformsCommandBinding.cs +++ b/src/ReactiveUI.Winforms/CreatesWinformsCommandBinding.cs @@ -4,30 +4,36 @@ // See the LICENSE file in the project root for full license information. using System.Reflection; +using System.Runtime.CompilerServices; using System.Windows.Input; namespace ReactiveUI.Winforms; /// -/// This binder is the default binder for connecting to arbitrary events. +/// Default command binder for Windows Forms controls that connects an to an event on a target object. /// -public class CreatesWinformsCommandBinding : ICreatesCommandBinding +/// +/// +/// This binder supports a small set of conventional "default" events (for example, Click, MouseUp), +/// and can also bind to an explicitly named event. +/// +/// +/// Reflection-based event lookup and string-based event subscription are not trimming/AOT-safe in general. +/// Use the generic overloads with explicit add/remove handler delegates to avoid the reflection cost. +/// +/// +public sealed class CreatesWinformsCommandBinding : ICreatesCommandBinding { // NB: These are in priority order private static readonly List<(string name, Type type)> _defaultEventsToBind = [ ("Click", typeof(EventArgs)), - ("MouseUp", typeof(System.Windows.Forms.MouseEventArgs)), - ]; + ("MouseUp", typeof(System.Windows.Forms.MouseEventArgs))]; /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif - public int GetAffinityForObject(Type type, bool hasEventTarget) + public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>(bool hasEventTarget) { - var isWinformControl = typeof(Control).IsAssignableFrom(type); + var isWinformControl = typeof(Control).IsAssignableFrom(typeof(T)); if (isWinformControl) { @@ -39,60 +45,51 @@ public int GetAffinityForObject(Type type, bool hasEventTarget) return 6; } - return _defaultEventsToBind.Any(x => + return _defaultEventsToBind.Any(static x => { - var ei = type.GetEvent(x.name, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance); + var ei = typeof(T).GetEvent(x.name, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance); return ei is not null; }) ? 4 : 0; } - /// -#if NET6_0_OR_GREATER - public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>( - bool hasEventTarget) -#else - public int GetAffinityForObject( - bool hasEventTarget) -#endif + /// + /// Binds a command to the default event on a Windows Forms control. + /// This method uses direct type checking and the AOT-safe add/remove handler overload instead of reflection. + /// + /// The type of the target object. + /// The command to bind. If , no binding is created. + /// The target object. + /// An observable that supplies command parameter values. + /// A disposable that unbinds the command, or null if no default event was found. + /// Thrown when is . + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(ICommand? command, T? target, IObservable commandParameter) + where T : class { - var isWinformControl = typeof(Control).IsAssignableFrom(typeof(T)); + ArgumentExceptionHelper.ThrowIfNull(target); - if (isWinformControl) + // Preserve typical binding semantics: null command => no-op binding. + if (command is null) { - return 10; + return Disposable.Empty; } - if (hasEventTarget) + // Use direct type checking for known WinForms types first (AOT-friendly) + if (target is Control control) { - return 6; + // Most controls have a Click event (uses non-generic EventHandler) + return BindCommandToObject( + command, + control, + commandParameter, + h => control.Click += h, + h => control.Click -= h); } - return _defaultEventsToBind.Any(static x => - { - var ei = typeof(T).GetEvent(x.name, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance); - return ei is not null; - }) ? 4 : 0; - } - - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif - public IDisposable? BindCommandToObject(ICommand? command, object? target, IObservable commandParameter) - { -#if NET6_0_OR_GREATER - ArgumentExceptionHelper.ThrowIfNull(command); - ArgumentExceptionHelper.ThrowIfNull(target); -#else - ArgumentExceptionHelper.ThrowIfNull(command); - - ArgumentExceptionHelper.ThrowIfNull(target); -#endif - + // Fall back to reflection-based event discovery for other types const BindingFlags bf = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy; - var type = target.GetType(); + var type = typeof(T); var eventInfo = _defaultEventsToBind .Select(x => new { EventInfo = type.GetEvent(x.name, bf), Args = x.type }) .FirstOrDefault(x => x.EventInfo is not null); @@ -102,27 +99,26 @@ public int GetAffinityForObject( return null; } - var mi = GetType().GetMethods().First(x => x.Name == "BindCommandToObject" && x.IsGenericMethod); - mi = mi.MakeGenericMethod(eventInfo.Args); + // Dynamically call the correct generic method based on event args type + if (eventInfo.Args == typeof(EventArgs)) + { + return BindCommandToObject(command, target, commandParameter, eventInfo.EventInfo?.Name!); + } + else if (eventInfo.Args == typeof(System.Windows.Forms.MouseEventArgs)) + { + return BindCommandToObject(command, target, commandParameter, eventInfo.EventInfo?.Name!); + } - return (IDisposable?)mi.Invoke(this, [command, target, commandParameter, eventInfo.EventInfo?.Name]); + return null; } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif - public IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter, string eventName) + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject(ICommand? command, T? target, IObservable commandParameter, string eventName) + where T : class { -#if NET6_0_OR_GREATER ArgumentExceptionHelper.ThrowIfNull(command); ArgumentExceptionHelper.ThrowIfNull(target); -#else - ArgumentExceptionHelper.ThrowIfNull(command); - - ArgumentExceptionHelper.ThrowIfNull(target); -#endif var ret = new CompositeDisposable(); @@ -164,4 +160,144 @@ public IDisposable BindCommandToObject(ICommand? command, object? ta return ret; } + + /// + /// Binds a command to an event on a target object using explicit add/remove handler delegates. + /// This overload is AOT-safe and doesn't require reflection. + /// + /// The type of the target object. + /// The type of the event arguments. + /// The command to bind. If , no binding is created. + /// The target object. + /// An observable that supplies command parameter values. + /// Action that subscribes an event handler to the target event. + /// Action that unsubscribes an event handler from the target event. + /// A disposable that unbinds the command. + /// Thrown when , , or is . + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>( + ICommand? command, + T? target, + IObservable commandParameter, + Action> addHandler, + Action> removeHandler) + where T : class + where TEventArgs : EventArgs + { + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(addHandler); + ArgumentExceptionHelper.ThrowIfNull(removeHandler); + + if (command is null) + { + return Disposable.Empty; + } + + object? latestParameter = null; + + void Handler(object? s, TEventArgs e) + { + var param = Volatile.Read(ref latestParameter); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } + + var ret = new CompositeDisposable(); + ret.Add(commandParameter.Subscribe(x => Volatile.Write(ref latestParameter, x))); + + addHandler(Handler); + ret.Add(Disposable.Create(() => removeHandler(Handler))); + + // Handle Enabled property binding for Components + var targetType = typeof(T); + if (typeof(Component).IsAssignableFrom(targetType)) + { + var enabledProperty = targetType.GetRuntimeProperty("Enabled"); + + if (enabledProperty is not null) + { + object? latestParam = null; + ret.Add(commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x))); + + ret.Add(Observable.FromEvent( + eventHandler => (_, _) => eventHandler(command.CanExecute(Volatile.Read(ref latestParam))), + x => command.CanExecuteChanged += x, + x => command.CanExecuteChanged -= x) + .StartWith(command.CanExecute(latestParam)) + .Subscribe(x => enabledProperty.SetValue(target, x, null))); + } + } + + return ret; + } + + /// + /// Binds a command to an event on a target object using explicit add/remove handler delegates for non-generic EventHandler. + /// This overload is AOT-safe and supports WinForms controls that use EventHandler instead of EventHandler<TEventArgs>. + /// + /// The type of the target object. + /// The command to bind. If , no binding is created. + /// The target object. + /// An observable that supplies command parameter values. + /// Action that subscribes an event handler to the target event. + /// Action that unsubscribes an event handler from the target event. + /// A disposable that unbinds the command. + /// Thrown when , , or is . + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T>( + ICommand? command, + T? target, + IObservable commandParameter, + Action addHandler, + Action removeHandler) + where T : class + { + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(addHandler); + ArgumentExceptionHelper.ThrowIfNull(removeHandler); + + if (command is null) + { + return Disposable.Empty; + } + + object? latestParameter = null; + + void Handler(object? s, EventArgs e) + { + var param = Volatile.Read(ref latestParameter); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } + + var ret = new CompositeDisposable(); + ret.Add(commandParameter.Subscribe(x => Volatile.Write(ref latestParameter, x))); + + addHandler(Handler); + ret.Add(Disposable.Create(() => removeHandler(Handler))); + + // Handle Enabled property binding for Components + var targetType = typeof(T); + if (typeof(Component).IsAssignableFrom(targetType)) + { + var enabledProperty = targetType.GetRuntimeProperty("Enabled"); + + if (enabledProperty is not null) + { + object? latestParam = null; + ret.Add(commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x))); + + ret.Add(Observable.FromEvent( + eventHandler => (_, _) => eventHandler(command.CanExecute(Volatile.Read(ref latestParam))), + x => command.CanExecuteChanged += x, + x => command.CanExecuteChanged -= x) + .StartWith(command.CanExecute(latestParam)) + .Subscribe(x => enabledProperty.SetValue(target, x, null))); + } + } + + return ret; + } } diff --git a/src/ReactiveUI.Winforms/PanelSetMethodBindingConverter.cs b/src/ReactiveUI.Winforms/PanelSetMethodBindingConverter.cs index ae2f7c9073..c6551b8a6c 100644 --- a/src/ReactiveUI.Winforms/PanelSetMethodBindingConverter.cs +++ b/src/ReactiveUI.Winforms/PanelSetMethodBindingConverter.cs @@ -11,10 +11,6 @@ namespace ReactiveUI.Winforms; public class PanelSetMethodBindingConverter : ISetMethodBindingConverter { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] -#endif public int GetAffinityForObjects(Type? fromType, Type? toType) { if (toType != typeof(Control.ControlCollection)) diff --git a/src/ReactiveUI.Winforms/ReactiveUI.Winforms.csproj b/src/ReactiveUI.Winforms/ReactiveUI.Winforms.csproj index 0ad1bfa290..92f17242f7 100644 --- a/src/ReactiveUI.Winforms/ReactiveUI.Winforms.csproj +++ b/src/ReactiveUI.Winforms/ReactiveUI.Winforms.csproj @@ -9,6 +9,9 @@ mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;frp;winforms;net;net472; $(NoWarn);IDE1006; True + false + false + false @@ -41,5 +44,6 @@ + diff --git a/src/ReactiveUI.Winforms/ReactiveUserControl.cs b/src/ReactiveUI.Winforms/ReactiveUserControl.cs index 91750c1605..f3425772b2 100644 --- a/src/ReactiveUI.Winforms/ReactiveUserControl.cs +++ b/src/ReactiveUI.Winforms/ReactiveUserControl.cs @@ -12,10 +12,6 @@ namespace ReactiveUI.Winforms; /// The type of the view model. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveUserControl provides base functionality for ReactiveUI which may require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveUserControl provides base functionality for ReactiveUI which may require unreferenced code")] -#endif public partial class ReactiveUserControl : UserControl, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI.Winforms/Registrations.cs b/src/ReactiveUI.Winforms/Registrations.cs index 7911c4d74d..cdb3e49068 100644 --- a/src/ReactiveUI.Winforms/Registrations.cs +++ b/src/ReactiveUI.Winforms/Registrations.cs @@ -12,28 +12,21 @@ namespace ReactiveUI.Winforms; public class Registrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); - registerFunction(static () => new PlatformOperations(), typeof(IPlatformOperations)); - - registerFunction(static () => new CreatesWinformsCommandBinding(), typeof(ICreatesCommandBinding)); - registerFunction(static () => new WinformsCreatesObservableForProperty(), typeof(ICreatesObservableForProperty)); - registerFunction(static () => new ActivationForViewFetcher(), typeof(IActivationForViewFetcher)); - registerFunction(static () => new PanelSetMethodBindingConverter(), typeof(ISetMethodBindingConverter)); - registerFunction(static () => new TableContentSetMethodBindingConverter(), typeof(ISetMethodBindingConverter)); - registerFunction(static () => new StringConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new SingleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new DoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new DecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new ComponentModelTypeConverter(), typeof(IBindingTypeConverter)); + registrar.RegisterConstant(static () => new PlatformOperations()); + registrar.RegisterConstant(static () => new CreatesWinformsCommandBinding()); + registrar.RegisterConstant(static () => new WinformsCreatesObservableForProperty()); + registrar.RegisterConstant(static () => new ActivationForViewFetcher()); + registrar.RegisterConstant(static () => new PanelSetMethodBindingConverter()); + registrar.RegisterConstant(static () => new TableContentSetMethodBindingConverter()); + registrar.RegisterConstant(static () => new StringConverter()); + registrar.RegisterConstant(static () => new SingleToStringTypeConverter()); + registrar.RegisterConstant(static () => new DoubleToStringTypeConverter()); + registrar.RegisterConstant(static () => new DecimalToStringTypeConverter()); + registrar.RegisterConstant(static () => new ComponentModelFallbackConverter()); if (!ModeDetector.InUnitTestRunner()) { diff --git a/src/ReactiveUI.Winforms/RoutedViewHost.cs b/src/ReactiveUI.Winforms/RoutedViewHost.cs index 4fa61d3158..717274c4df 100644 --- a/src/ReactiveUI.Winforms/RoutedViewHost.cs +++ b/src/ReactiveUI.Winforms/RoutedViewHost.cs @@ -9,10 +9,6 @@ namespace ReactiveUI.Winforms; /// A control host which will handling routing between different ViewModels and Views. /// [DefaultProperty("ViewModel")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("RoutedControlHost uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("RoutedControlHost uses methods that may require unreferenced code")] -#endif public partial class RoutedControlHost : UserControl, IReactiveObject { private readonly CompositeDisposable _disposables = []; @@ -25,10 +21,6 @@ public partial class RoutedControlHost : UserControl, IReactiveObject /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutedControlHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("RoutedControlHost uses methods that may require unreferenced code")] -#endif public RoutedControlHost() { InitializeComponent(); @@ -89,7 +81,7 @@ public RoutedControlHost() ResumeLayout(); }, - RxApp.DefaultExceptionHandler!.OnNext)); + RxState.DefaultExceptionHandler!.OnNext)); } /// diff --git a/src/ReactiveUI.Winforms/TableContentSetMethodBindingConverter.cs b/src/ReactiveUI.Winforms/TableContentSetMethodBindingConverter.cs index 93d34c6753..fa879f381a 100644 --- a/src/ReactiveUI.Winforms/TableContentSetMethodBindingConverter.cs +++ b/src/ReactiveUI.Winforms/TableContentSetMethodBindingConverter.cs @@ -11,15 +11,11 @@ namespace ReactiveUI.Winforms; public class TableContentSetMethodBindingConverter : ISetMethodBindingConverter { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] -#endif public int GetAffinityForObjects(Type? fromType, Type? toType) => toType != typeof(TableLayoutControlCollection) ? 0 : fromType?.GetInterfaces().Any(static x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>) && x.GetGenericArguments()[0].IsSubclassOf(typeof(Control))) ?? false - ? 15 + ? 10 : 0; /// diff --git a/src/ReactiveUI.Winforms/ViewModelViewHost.cs b/src/ReactiveUI.Winforms/ViewModelViewHost.cs index dcafb619c5..f2b225b41e 100644 --- a/src/ReactiveUI.Winforms/ViewModelViewHost.cs +++ b/src/ReactiveUI.Winforms/ViewModelViewHost.cs @@ -8,11 +8,13 @@ namespace ReactiveUI.Winforms; /// /// A view model control host which will find and host the View for a ViewModel. /// +/// +/// This class uses reflection to determine view model types at runtime through ViewLocator. +/// For AOT-compatible scenarios, use ViewModelControlHost<TViewModel> instead. +/// [DefaultProperty("ViewModel")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ViewModelControlHost uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ViewModelControlHost uses methods that may require unreferenced code")] -#endif +[RequiresUnreferencedCode("This class uses reflection to determine view model types at runtime through ViewLocator, which may be incompatible with trimming.")] +[RequiresDynamicCode("ViewLocator.ResolveView uses reflection which is incompatible with AOT compilation.")] public partial class ViewModelControlHost : UserControl, IReactiveObject, IViewFor { private readonly CompositeDisposable _disposables = []; @@ -27,10 +29,6 @@ public partial class ViewModelControlHost : UserControl, IReactiveObject, IViewF /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ViewModelControlHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ViewModelControlHost uses methods that may require unreferenced code")] -#endif public ViewModelControlHost() { InitializeComponent(); @@ -146,10 +144,6 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SetupBindings uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SetupBindings uses methods that may require unreferenced code")] -#endif private IEnumerable SetupBindings() { var viewChanges = @@ -228,6 +222,6 @@ private IEnumerable SetupBindings() Content = view; } }, - RxApp.DefaultExceptionHandler!.OnNext); + RxState.DefaultExceptionHandler!.OnNext); } } diff --git a/src/ReactiveUI.Winforms/WinformsCreatesObservableForProperty.cs b/src/ReactiveUI.Winforms/WinformsCreatesObservableForProperty.cs index c3bc9b0cb7..cd36ed9012 100644 --- a/src/ReactiveUI.Winforms/WinformsCreatesObservableForProperty.cs +++ b/src/ReactiveUI.Winforms/WinformsCreatesObservableForProperty.cs @@ -12,21 +12,14 @@ namespace ReactiveUI.Winforms; /// particularly useful types. /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("WinformsCreatesObservableForProperty uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("WinformsCreatesObservableForProperty uses methods that may require unreferenced code")] -#endif public class WinformsCreatesObservableForProperty : ICreatesObservableForProperty { private static readonly MemoizingMRUCache<(Type type, string name), EventInfo?> EventInfoCache = new( static (pair, _) => pair.type.GetEvent(pair.name + "Changed", BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public), - RxApp.SmallCacheLimit); + RxCacheSize.SmallCacheLimit); /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) { var supportsTypeBinding = typeof(Component).IsAssignableFrom(type); @@ -40,10 +33,7 @@ public int GetAffinityForObject(Type type, string propertyName, bool beforeChang } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { ArgumentExceptionHelper.ThrowIfNull(sender); @@ -70,7 +60,7 @@ public int GetAffinityForObject(Type type, string propertyName, bool beforeChang } }); - var scheduler = RxApp.MainThreadScheduler; + var scheduler = RxSchedulers.MainThreadScheduler; ei.AddEventHandler(sender, handler); return Disposable.Create(() => scheduler.Schedule(() => ei.RemoveEventHandler(sender, handler))); diff --git a/src/ReactiveUI.Wpf/ActivationForViewFetcher.cs b/src/ReactiveUI.Wpf/ActivationForViewFetcher.cs index 4e6728a230..ff81f99023 100644 --- a/src/ReactiveUI.Wpf/ActivationForViewFetcher.cs +++ b/src/ReactiveUI.Wpf/ActivationForViewFetcher.cs @@ -19,10 +19,6 @@ public class ActivationForViewFetcher : IActivationForViewFetcher public int GetAffinityForView(Type view) => typeof(FrameworkElement).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0; /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] -#endif public IObservable GetActivationForView(IActivatableView view) { if (view is not FrameworkElement fe) diff --git a/src/ReactiveUI.Wpf/AutoSuspendHelper.cs b/src/ReactiveUI.Wpf/AutoSuspendHelper.cs index 0afd55d378..25dbcde52b 100644 --- a/src/ReactiveUI.Wpf/AutoSuspendHelper.cs +++ b/src/ReactiveUI.Wpf/AutoSuspendHelper.cs @@ -8,7 +8,7 @@ namespace ReactiveUI; /// -/// Wires WPF lifecycle events into so application state can be persisted automatically. +/// Wires WPF lifecycle events into so application state can be persisted automatically. /// /// /// @@ -33,18 +33,14 @@ namespace ReactiveUI; /// IdleTimeout = TimeSpan.FromSeconds(10) /// }; /// -/// RxApp.SuspensionHost.CreateNewAppState = () => new ShellState(); -/// RxApp.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(LocalAppDataProvider.Resolve())); +/// RxSuspension.SuspensionHost.CreateNewAppState = () => new ShellState(); +/// RxSuspension.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(LocalAppDataProvider.Resolve())); /// } /// } /// ]]> /// /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("AutoSuspendHelper uses RxApp.SuspensionHost and TaskpoolScheduler which require dynamic code generation")] -[RequiresUnreferencedCode("AutoSuspendHelper uses RxApp.SuspensionHost and TaskpoolScheduler which may require unreferenced code")] -#endif public class AutoSuspendHelper : IEnableLogger { /// @@ -55,7 +51,7 @@ public AutoSuspendHelper(Application app) { IdleTimeout = TimeSpan.FromSeconds(15.0); - RxApp.SuspensionHost.IsLaunchingNew = + RxSuspension.SuspensionHost.IsLaunchingNew = Observable.FromEvent( eventHandler => { @@ -65,13 +61,13 @@ public AutoSuspendHelper(Application app) x => app.Startup += x, x => app.Startup -= x); - RxApp.SuspensionHost.IsUnpausing = + RxSuspension.SuspensionHost.IsUnpausing = Observable.FromEvent( eventHandler => (_, _) => eventHandler(Unit.Default), x => app.Activated += x, x => app.Activated -= x); - RxApp.SuspensionHost.IsResuming = Observable.Never; + RxSuspension.SuspensionHost.IsResuming = Observable.Never; // NB: No way to tell OS that we need time to suspend, we have to // do it in-process @@ -89,16 +85,16 @@ public AutoSuspendHelper(Application app) x => app.Exit += x, x => app.Exit -= x); - RxApp.SuspensionHost.ShouldPersistState = exit.Merge( + RxSuspension.SuspensionHost.ShouldPersistState = exit.Merge( deactivated - .SelectMany(_ => Observable.Timer(IdleTimeout, RxApp.TaskpoolScheduler)) - .TakeUntil(RxApp.SuspensionHost.IsUnpausing) + .SelectMany(_ => Observable.Timer(IdleTimeout, RxSchedulers.TaskpoolScheduler)) + .TakeUntil(RxSuspension.SuspensionHost.IsUnpausing) .Repeat() .Select(_ => Disposable.Empty)); var untimelyDeath = new Subject(); AppDomain.CurrentDomain.UnhandledException += (_, _) => untimelyDeath.OnNext(Unit.Default); - RxApp.SuspensionHost.ShouldInvalidateState = untimelyDeath; + RxSuspension.SuspensionHost.ShouldInvalidateState = untimelyDeath; } /// diff --git a/src/ReactiveUI.Wpf/Binding/ValidationBindingMixins.cs b/src/ReactiveUI.Wpf/Binding/ValidationBindingMixins.cs index 16fe5ecd1a..9ebc5e3d3a 100644 --- a/src/ReactiveUI.Wpf/Binding/ValidationBindingMixins.cs +++ b/src/ReactiveUI.Wpf/Binding/ValidationBindingMixins.cs @@ -29,10 +29,6 @@ public static class ValidationBindingMixins /// An instance of that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindWithValidation uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("BindWithValidation uses methods that may require unreferenced code")] -#endif public static IReactiveBinding BindWithValidation(this TView view, TViewModel viewModel, Expression> viewModelPropertySelector, Expression> frameworkElementSelector) where TView : class, IViewFor where TViewModel : class diff --git a/src/ReactiveUI.Wpf/Binding/ValidationBindingWpf.cs b/src/ReactiveUI.Wpf/Binding/ValidationBindingWpf.cs index bb4e4bf9a0..9b893b4291 100644 --- a/src/ReactiveUI.Wpf/Binding/ValidationBindingWpf.cs +++ b/src/ReactiveUI.Wpf/Binding/ValidationBindingWpf.cs @@ -32,10 +32,6 @@ internal class ValidationBindingWpf : IReact private readonly string _vmPropertyName; private IDisposable? _inner; -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ValidationBindingWpf uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ValidationBindingWpf uses methods that may require unreferenced code")] -#endif public ValidationBindingWpf( TView view, TViewModel viewModel, diff --git a/src/ReactiveUI.Wpf/Builder/WpfReactiveUIBuilderExtensions.cs b/src/ReactiveUI.Wpf/Builder/WpfReactiveUIBuilderExtensions.cs index 3d3f74f7aa..8f9c6b7583 100644 --- a/src/ReactiveUI.Wpf/Builder/WpfReactiveUIBuilderExtensions.cs +++ b/src/ReactiveUI.Wpf/Builder/WpfReactiveUIBuilderExtensions.cs @@ -3,6 +3,8 @@ // 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 Splat.Builder; + namespace ReactiveUI.Builder; /// @@ -33,6 +35,13 @@ public static IReactiveUIBuilder WithWpf(this IReactiveUIBuilder builder) .WithWpfScheduler(); } + /// + /// Configures ReactiveUI for WPF platform with appropriate schedulers. + /// + /// The builder instance. + /// The builder instance for chaining. + public static IReactiveUIBuilder WithWpf(this IAppBuilder builder) => ((IReactiveUIBuilder)builder).WithWpf(); + /// /// Withes the WPF scheduler. /// diff --git a/src/ReactiveUI.Wpf/Common/AutoDataTemplateBindingHook.cs b/src/ReactiveUI.Wpf/Common/AutoDataTemplateBindingHook.cs index beb285f8b0..adf5d53010 100644 --- a/src/ReactiveUI.Wpf/Common/AutoDataTemplateBindingHook.cs +++ b/src/ReactiveUI.Wpf/Common/AutoDataTemplateBindingHook.cs @@ -33,10 +33,6 @@ public class AutoDataTemplateBindingHook : IPropertyBindingHook }); /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] -#endif public bool ExecuteHook(object? source, object target, Func[]> getCurrentViewModelProperties, Func[]> getCurrentViewProperties, BindingDirection direction) { ArgumentExceptionHelper.ThrowIfNull(getCurrentViewProperties); diff --git a/src/ReactiveUI.Wpf/Common/BooleanToVisibilityTypeConverter.cs b/src/ReactiveUI.Wpf/Common/BooleanToVisibilityTypeConverter.cs index aee8c30278..3bfa3dd71c 100644 --- a/src/ReactiveUI.Wpf/Common/BooleanToVisibilityTypeConverter.cs +++ b/src/ReactiveUI.Wpf/Common/BooleanToVisibilityTypeConverter.cs @@ -3,6 +3,8 @@ // 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 HAS_MAUI using Microsoft.Maui; @@ -22,56 +24,44 @@ namespace ReactiveUI; #endif /// -/// This type convert converts between Boolean and XAML Visibility - the -/// conversionHint is a BooleanToVisibilityHint. +/// Converts to . /// -public class BooleanToVisibilityTypeConverter : IBindingTypeConverter +/// +/// +/// The conversion supports a as the conversion hint parameter: +/// +/// +/// - True maps to Visible, False maps to Collapsed. +/// - Inverts the boolean before conversion (True → Collapsed, False → Visible). +/// - Use Hidden instead of Collapsed for false values (WPF only, ignored on UNO/WinUI). +/// +/// +/// Hints can be combined using bitwise OR (e.g., BooleanToVisibilityHint.Inverse | BooleanToVisibilityHint.UseHidden). +/// +/// +public sealed class BooleanToVisibilityTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(bool) && toType == typeof(Visibility)) - { - return 10; - } - - if (fromType == typeof(Visibility) && toType == typeof(bool)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(bool from, object? conversionHint, [NotNullWhen(true)] out Visibility result) { - var hint = conversionHint is BooleanToVisibilityHint visibilityHint ? - visibilityHint : - BooleanToVisibilityHint.None; + var hint = conversionHint is BooleanToVisibilityHint visibilityHint + ? visibilityHint + : BooleanToVisibilityHint.None; - if (toType == typeof(Visibility) && from is bool fromBool) - { - var fromAsBool = (hint & BooleanToVisibilityHint.Inverse) != 0 ? !fromBool : fromBool; + var value = (hint & BooleanToVisibilityHint.Inverse) != 0 ? !from : from; #if !HAS_UNO && !HAS_WINUI - var notVisible = (hint & BooleanToVisibilityHint.UseHidden) != 0 ? Visibility.Hidden : Visibility.Collapsed; + var notVisible = (hint & BooleanToVisibilityHint.UseHidden) != 0 + ? Visibility.Hidden + : Visibility.Collapsed; #else - var notVisible = Visibility.Collapsed; + var notVisible = Visibility.Collapsed; #endif - result = fromAsBool ? Visibility.Visible : notVisible; - return true; - } - - if (from is Visibility fromAsVis) - { - result = fromAsVis == Visibility.Visible ^ (hint & BooleanToVisibilityHint.Inverse) == 0; - } - else - { - result = Visibility.Visible; - } + result = value ? Visibility.Visible : notVisible; return true; } } diff --git a/src/ReactiveUI.Wpf/Common/RoutedViewHost.cs b/src/ReactiveUI.Wpf/Common/RoutedViewHost.cs index a4beffef27..05d945afa3 100644 --- a/src/ReactiveUI.Wpf/Common/RoutedViewHost.cs +++ b/src/ReactiveUI.Wpf/Common/RoutedViewHost.cs @@ -59,10 +59,6 @@ class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLog /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] -#endif public RoutedViewHost() { HorizontalContentAlignment = HorizontalAlignment.Stretch; @@ -79,7 +75,7 @@ public RoutedViewHost() } else { - platformGetter = () => platform.GetOrientation(); + platformGetter = platform.GetOrientation; } ViewContractObservable = ModeDetector.InUnitTestRunner() @@ -106,7 +102,7 @@ public RoutedViewHost() this.WhenActivated(d => d(vmAndContract.DistinctUntilChanged().Subscribe( ResolveViewForViewModel, - ex => RxApp.DefaultExceptionHandler.OnNext(ex)))); + ex => RxState.DefaultExceptionHandler.OnNext(ex)))); } /// @@ -157,10 +153,6 @@ public string? ViewContract /// public IViewLocator? ViewLocator { get; set; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] -#endif private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) x) { if (x.viewModel is null) diff --git a/src/ReactiveUI.Wpf/Common/ViewModelViewHost.cs b/src/ReactiveUI.Wpf/Common/ViewModelViewHost.cs index 2c25ed620b..e7c866c02b 100644 --- a/src/ReactiveUI.Wpf/Common/ViewModelViewHost.cs +++ b/src/ReactiveUI.Wpf/Common/ViewModelViewHost.cs @@ -33,10 +33,6 @@ namespace ReactiveUI; /// the ViewModel property and display it. This control is very useful /// inside a DataTemplate to display the View associated with a ViewModel. /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp which require dynamic code generation")] -[RequiresUnreferencedCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp which may require unreferenced code")] -#endif public #if HAS_UNO partial diff --git a/src/ReactiveUI.Wpf/Common/VisibilityToBooleanTypeConverter.cs b/src/ReactiveUI.Wpf/Common/VisibilityToBooleanTypeConverter.cs new file mode 100644 index 0000000000..bbf06e8320 --- /dev/null +++ b/src/ReactiveUI.Wpf/Common/VisibilityToBooleanTypeConverter.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2025 .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 HAS_MAUI +using Microsoft.Maui; + +#endif +#if HAS_WINUI +using Microsoft.UI.Xaml; +#elif HAS_UNO +using Windows.UI.Xaml; +#else +using System.Windows; +#endif + +#if HAS_UNO +namespace ReactiveUI.Uno +#else +namespace ReactiveUI; +#endif + +/// +/// Converts to . +/// +/// +/// +/// The conversion supports a as the conversion hint parameter: +/// +/// +/// - Visible maps to True, other values map to False. +/// - Inverts the result (Visible → False, other → True). +/// +/// +/// This converter enables two-way binding between boolean properties and visibility. +/// +/// +public sealed class VisibilityToBooleanTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(Visibility from, object? conversionHint, [NotNullWhen(true)] out bool result) + { + var hint = conversionHint is BooleanToVisibilityHint visibilityHint + ? visibilityHint + : BooleanToVisibilityHint.None; + + var isVisible = from == Visibility.Visible; + result = (hint & BooleanToVisibilityHint.Inverse) != 0 ? !isVisible : isVisible; + return true; + } +} diff --git a/src/ReactiveUI.Wpf/DependencyObjectObservableForProperty.cs b/src/ReactiveUI.Wpf/DependencyObjectObservableForProperty.cs index 24b445628a..a68cbb36c0 100644 --- a/src/ReactiveUI.Wpf/DependencyObjectObservableForProperty.cs +++ b/src/ReactiveUI.Wpf/DependencyObjectObservableForProperty.cs @@ -14,10 +14,6 @@ namespace ReactiveUI; public class DependencyObjectObservableForProperty : ICreatesObservableForProperty { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) { if (!typeof(DependencyObject).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) @@ -29,10 +25,6 @@ public int GetAffinityForObject(Type type, string propertyName, bool beforeChang } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] -#endif public IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { ArgumentExceptionHelper.ThrowIfNull(sender); @@ -57,17 +49,13 @@ public int GetAffinityForObject(Type type, string propertyName, bool beforeChang return Observable.Create>(subj => { var handler = new EventHandler((_, _) => subj.OnNext(new ObservedChange(sender, expression, default))); - var scheduler = RxApp.MainThreadScheduler; + var scheduler = RxSchedulers.MainThreadScheduler; dependencyPropertyDescriptor.AddValueChanged(sender, handler); return Disposable.Create(() => scheduler.Schedule(() => dependencyPropertyDescriptor.RemoveValueChanged(sender, handler))); }); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetDependencyProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetDependencyProperty uses methods that may require unreferenced code")] -#endif private static DependencyProperty? GetDependencyProperty(Type type, string propertyName) { var fi = Array.Find(type.GetTypeInfo().GetFields(BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public), x => x.Name == propertyName + "Property" && x.IsStatic); diff --git a/src/ReactiveUI.Wpf/ReactiveUI.Wpf.csproj b/src/ReactiveUI.Wpf/ReactiveUI.Wpf.csproj index fe4e9a40b3..2104ea4586 100644 --- a/src/ReactiveUI.Wpf/ReactiveUI.Wpf.csproj +++ b/src/ReactiveUI.Wpf/ReactiveUI.Wpf.csproj @@ -42,5 +42,6 @@ + diff --git a/src/ReactiveUI.Wpf/Registrations.cs b/src/ReactiveUI.Wpf/Registrations.cs index 3dafd8e599..fb66a078ad 100644 --- a/src/ReactiveUI.Wpf/Registrations.cs +++ b/src/ReactiveUI.Wpf/Registrations.cs @@ -11,27 +11,20 @@ namespace ReactiveUI.Wpf; public class Registrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); - registerFunction(static () => new PlatformOperations(), typeof(IPlatformOperations)); + registrar.RegisterConstant(static () => new PlatformOperations()); + registrar.RegisterConstant(static () => new ActivationForViewFetcher()); + registrar.RegisterConstant(static () => new DependencyObjectObservableForProperty()); - registerFunction(static () => new ActivationForViewFetcher(), typeof(IActivationForViewFetcher)); - registerFunction(static () => new DependencyObjectObservableForProperty(), typeof(ICreatesObservableForProperty)); - registerFunction(static () => new StringConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new SingleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new DoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new DecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new BooleanToVisibilityTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); - registerFunction(static () => new ComponentModelTypeConverter(), typeof(IBindingTypeConverter)); + // WPF-specific converters + registrar.RegisterConstant(static () => new BooleanToVisibilityTypeConverter()); + registrar.RegisterConstant(static () => new VisibilityToBooleanTypeConverter()); + + registrar.RegisterConstant(static () => new AutoDataTemplateBindingHook()); + registrar.RegisterConstant(static () => new ComponentModelFallbackConverter()); if (!ModeDetector.InUnitTestRunner()) { @@ -40,6 +33,6 @@ public void Register(Action, Type> registerFunction) RxSchedulers.TaskpoolScheduler = TaskPoolScheduler.Default; } - RxApp.SuppressViewCommandBindingMessage = true; + RxSchedulers.SuppressViewCommandBindingMessage = true; } } diff --git a/src/ReactiveUI.Wpf/Rx/Linq/DispatcherObservable.cs b/src/ReactiveUI.Wpf/Rx/Linq/DispatcherObservable.cs index 5560683b77..19787135a7 100644 --- a/src/ReactiveUI.Wpf/Rx/Linq/DispatcherObservable.cs +++ b/src/ReactiveUI.Wpf/Rx/Linq/DispatcherObservable.cs @@ -67,7 +67,6 @@ public static IObservable ObserveOn(this IObservable return ObserveOn_(source, scheduler.Dispatcher, scheduler.Priority); } - /// /// Wraps the source sequence in order to run its observer callbacks on the dispatcher associated with the specified object. /// @@ -108,7 +107,6 @@ private static IObservable ObserveOn_(IObservable sou return Synchronization.ObserveOn(source, new DispatcherSynchronizationContext(dispatcher, priority)); } - private static IObservable ObserveOn_(IObservable source, Dispatcher dispatcher) { return Synchronization.ObserveOn(source, new DispatcherSynchronizationContext(dispatcher)); @@ -161,7 +159,6 @@ public static IObservable SubscribeOn(this IObservable /// Wraps the source sequence in order to run its subscription and unsubscription logic on the specified dispatcher scheduler. /// @@ -231,7 +228,6 @@ private static IObservable SubscribeOn_(IObservable s return Synchronization.SubscribeOn(source, new DispatcherSynchronizationContext(dispatcher, priority)); } - private static IObservable SubscribeOn_(IObservable source, Dispatcher dispatcher) { return Synchronization.SubscribeOn(source, new DispatcherSynchronizationContext(dispatcher)); diff --git a/src/ReactiveUI.sln b/src/ReactiveUI.sln deleted file mode 100644 index ff27163d7a..0000000000 --- a/src/ReactiveUI.sln +++ /dev/null @@ -1,408 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.0.11018.127 -MinimumVisualStudioVersion = 16.0.31613.86 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BD9762CF-E104-481C-96A6-26E624B86283}" - ProjectSection(SolutionItems) = preProject - ..\.editorconfig = ..\.editorconfig - Directory.Build.props = Directory.Build.props - Directory.Build.targets = Directory.Build.targets - Directory.Packages.props = Directory.Packages.props - stylecop.json = stylecop.json - ..\version.json = ..\version.json - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI", "ReactiveUI\ReactiveUI.csproj", "{464CB812-F99F-401B-BE4C-E8F0515CD19D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Testing", "ReactiveUI.Testing\ReactiveUI.Testing.csproj", "{DDF89A7A-5CC9-4243-98E4-462860D5D963}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Blend", "ReactiveUI.Blend\ReactiveUI.Blend.csproj", "{C11F6165-6142-476F-83F1-CFEBC330C769}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Winforms", "ReactiveUI.Winforms\ReactiveUI.Winforms.csproj", "{B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Tests", "tests\ReactiveUI.Tests\ReactiveUI.Tests.csproj", "{2ADE0A50-5012-4341-8F4F-97597C2D6920}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Wpf", "ReactiveUI.Wpf\ReactiveUI.Wpf.csproj", "{15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Splat.Tests", "tests\ReactiveUI.Splat.Tests\ReactiveUI.Splat.Tests.csproj", "{7ED6D69F-138F-40BD-9F37-3E4050E4D19B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Testing.Tests", "tests\ReactiveUI.Testing.Tests\ReactiveUI.Testing.Tests.csproj", "{CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Blazor", "ReactiveUI.Blazor\ReactiveUI.Blazor.csproj", "{0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Drawing", "ReactiveUI.Drawing\ReactiveUI.Drawing.csproj", "{999D555D-C567-457C-95F7-8AD61310C56E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{2AE709FA-BE58-4287-BFD3-E80BEB605125}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{3F9A8B1C-5D2E-4A7F-9B8C-1E6D4F5A7B9C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Maui", "ReactiveUI.Maui\ReactiveUI.Maui.csproj", "{3C552CA0-C364-4E51-8825-35D082C1D5BA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.WinUI", "ReactiveUI.WinUI\ReactiveUI.WinUI.csproj", "{4FF9F04B-928E-47B6-836F-546B584F597C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.AndroidX", "ReactiveUI.AndroidX\ReactiveUI.AndroidX.csproj", "{3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.AOT.Tests", "tests\ReactiveUI.AOTTests\ReactiveUI.AOT.Tests.csproj", "{D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Builder.Tests", "tests\ReactiveUI.Builder.Tests\ReactiveUI.Builder.Tests.csproj", "{8034024A-2804-4920-921A-86ADCCBF0838}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Builder.Maui.Tests", "tests\ReactiveUI.Builder.Maui.Tests\ReactiveUI.Builder.Maui.Tests.csproj", "{2A4B719C-4958-AD92-4A22-6472AAF98A37}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Builder.WpfApp", "examples\ReactiveUI.Builder.WpfApp\ReactiveUI.Builder.WpfApp.csproj", "{B5B3101B-6638-418D-AE82-59984D9D6AC7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.NonParallel.Tests", "tests\ReactiveUI.NonParallel.Tests\ReactiveUI.NonParallel.Tests.csproj", "{C4338ACD-DFDC-1724-6F35-36519B285D1A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Blazor.Tests", "tests\ReactiveUI.Blazor.Tests\ReactiveUI.Blazor.Tests.csproj", "{928C9161-B7A5-41EA-991C-C4FB44605F97}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Maui.Tests", "tests\ReactiveUI.Maui.Tests\ReactiveUI.Maui.Tests.csproj", "{EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|arm64 = Debug|arm64 - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|arm64 = Release|arm64 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Debug|arm64.ActiveCfg = Debug|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Debug|arm64.Build.0 = Debug|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Debug|x64.ActiveCfg = Debug|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Debug|x64.Build.0 = Debug|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Debug|x86.ActiveCfg = Debug|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Debug|x86.Build.0 = Debug|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Release|Any CPU.Build.0 = Release|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Release|arm64.ActiveCfg = Release|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Release|arm64.Build.0 = Release|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Release|x64.ActiveCfg = Release|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Release|x64.Build.0 = Release|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Release|x86.ActiveCfg = Release|Any CPU - {464CB812-F99F-401B-BE4C-E8F0515CD19D}.Release|x86.Build.0 = Release|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Debug|arm64.ActiveCfg = Debug|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Debug|arm64.Build.0 = Debug|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Debug|x64.ActiveCfg = Debug|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Debug|x64.Build.0 = Debug|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Debug|x86.ActiveCfg = Debug|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Debug|x86.Build.0 = Debug|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Release|Any CPU.Build.0 = Release|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Release|arm64.ActiveCfg = Release|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Release|arm64.Build.0 = Release|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Release|x64.ActiveCfg = Release|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Release|x64.Build.0 = Release|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Release|x86.ActiveCfg = Release|Any CPU - {DDF89A7A-5CC9-4243-98E4-462860D5D963}.Release|x86.Build.0 = Release|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Debug|arm64.ActiveCfg = Debug|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Debug|arm64.Build.0 = Debug|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Debug|x64.ActiveCfg = Debug|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Debug|x64.Build.0 = Debug|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Debug|x86.ActiveCfg = Debug|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Debug|x86.Build.0 = Debug|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Release|Any CPU.Build.0 = Release|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Release|arm64.ActiveCfg = Release|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Release|arm64.Build.0 = Release|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Release|x64.ActiveCfg = Release|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Release|x64.Build.0 = Release|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Release|x86.ActiveCfg = Release|Any CPU - {C11F6165-6142-476F-83F1-CFEBC330C769}.Release|x86.Build.0 = Release|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Debug|arm64.ActiveCfg = Debug|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Debug|arm64.Build.0 = Debug|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Debug|x64.ActiveCfg = Debug|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Debug|x64.Build.0 = Debug|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Debug|x86.ActiveCfg = Debug|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Debug|x86.Build.0 = Debug|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Release|Any CPU.Build.0 = Release|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Release|arm64.ActiveCfg = Release|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Release|arm64.Build.0 = Release|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Release|x64.ActiveCfg = Release|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Release|x64.Build.0 = Release|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Release|x86.ActiveCfg = Release|Any CPU - {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D}.Release|x86.Build.0 = Release|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Debug|arm64.ActiveCfg = Debug|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Debug|arm64.Build.0 = Debug|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Debug|x64.ActiveCfg = Debug|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Debug|x64.Build.0 = Debug|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Debug|x86.ActiveCfg = Debug|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Debug|x86.Build.0 = Debug|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Release|Any CPU.Build.0 = Release|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Release|arm64.ActiveCfg = Release|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Release|arm64.Build.0 = Release|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Release|x64.ActiveCfg = Release|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Release|x64.Build.0 = Release|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Release|x86.ActiveCfg = Release|Any CPU - {2ADE0A50-5012-4341-8F4F-97597C2D6920}.Release|x86.Build.0 = Release|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Debug|arm64.ActiveCfg = Debug|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Debug|arm64.Build.0 = Debug|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Debug|x64.ActiveCfg = Debug|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Debug|x64.Build.0 = Debug|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Debug|x86.ActiveCfg = Debug|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Debug|x86.Build.0 = Debug|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Release|Any CPU.Build.0 = Release|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Release|arm64.ActiveCfg = Release|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Release|arm64.Build.0 = Release|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Release|x64.ActiveCfg = Release|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Release|x64.Build.0 = Release|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Release|x86.ActiveCfg = Release|Any CPU - {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6}.Release|x86.Build.0 = Release|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Debug|arm64.ActiveCfg = Debug|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Debug|arm64.Build.0 = Debug|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Debug|x64.ActiveCfg = Debug|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Debug|x64.Build.0 = Debug|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Debug|x86.ActiveCfg = Debug|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Debug|x86.Build.0 = Debug|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Release|Any CPU.Build.0 = Release|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Release|arm64.ActiveCfg = Release|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Release|arm64.Build.0 = Release|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Release|x64.ActiveCfg = Release|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Release|x64.Build.0 = Release|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Release|x86.ActiveCfg = Release|Any CPU - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B}.Release|x86.Build.0 = Release|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Debug|arm64.ActiveCfg = Debug|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Debug|arm64.Build.0 = Debug|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Debug|x64.ActiveCfg = Debug|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Debug|x64.Build.0 = Debug|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Debug|x86.ActiveCfg = Debug|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Debug|x86.Build.0 = Debug|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Release|Any CPU.Build.0 = Release|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Release|arm64.ActiveCfg = Release|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Release|arm64.Build.0 = Release|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Release|x64.ActiveCfg = Release|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Release|x64.Build.0 = Release|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Release|x86.ActiveCfg = Release|Any CPU - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684}.Release|x86.Build.0 = Release|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Debug|arm64.ActiveCfg = Debug|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Debug|x64.ActiveCfg = Debug|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Debug|x86.ActiveCfg = Debug|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Release|Any CPU.Build.0 = Release|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Release|arm64.ActiveCfg = Release|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Release|x64.ActiveCfg = Release|Any CPU - {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3}.Release|x86.ActiveCfg = Release|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Debug|arm64.ActiveCfg = Debug|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Debug|arm64.Build.0 = Debug|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Debug|x64.ActiveCfg = Debug|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Debug|x64.Build.0 = Debug|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Debug|x86.ActiveCfg = Debug|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Debug|x86.Build.0 = Debug|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Release|Any CPU.Build.0 = Release|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Release|arm64.ActiveCfg = Release|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Release|arm64.Build.0 = Release|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Release|x64.ActiveCfg = Release|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Release|x64.Build.0 = Release|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Release|x86.ActiveCfg = Release|Any CPU - {999D555D-C567-457C-95F7-8AD61310C56E}.Release|x86.Build.0 = Release|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Debug|arm64.ActiveCfg = Debug|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Debug|arm64.Build.0 = Debug|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Debug|x64.ActiveCfg = Debug|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Debug|x64.Build.0 = Debug|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Debug|x86.ActiveCfg = Debug|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Debug|x86.Build.0 = Debug|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Release|Any CPU.Build.0 = Release|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Release|arm64.ActiveCfg = Release|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Release|arm64.Build.0 = Release|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Release|x64.ActiveCfg = Release|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Release|x64.Build.0 = Release|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Release|x86.ActiveCfg = Release|Any CPU - {3C552CA0-C364-4E51-8825-35D082C1D5BA}.Release|x86.Build.0 = Release|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Debug|arm64.ActiveCfg = Debug|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Debug|arm64.Build.0 = Debug|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Debug|x64.ActiveCfg = Debug|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Debug|x64.Build.0 = Debug|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Debug|x86.ActiveCfg = Debug|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Debug|x86.Build.0 = Debug|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Release|Any CPU.Build.0 = Release|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Release|arm64.ActiveCfg = Release|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Release|arm64.Build.0 = Release|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Release|x64.ActiveCfg = Release|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Release|x64.Build.0 = Release|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Release|x86.ActiveCfg = Release|Any CPU - {4FF9F04B-928E-47B6-836F-546B584F597C}.Release|x86.Build.0 = Release|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|arm64.ActiveCfg = Debug|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|arm64.Build.0 = Debug|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|x64.ActiveCfg = Debug|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|x64.Build.0 = Debug|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|x86.ActiveCfg = Debug|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|x86.Build.0 = Debug|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|Any CPU.Build.0 = Release|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|arm64.ActiveCfg = Release|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|arm64.Build.0 = Release|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|x64.ActiveCfg = Release|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|x64.Build.0 = Release|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|x86.ActiveCfg = Release|Any CPU - {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|x86.Build.0 = Release|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Debug|arm64.ActiveCfg = Debug|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Debug|arm64.Build.0 = Debug|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Debug|x64.ActiveCfg = Debug|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Debug|x64.Build.0 = Debug|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Debug|x86.ActiveCfg = Debug|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Debug|x86.Build.0 = Debug|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Release|Any CPU.Build.0 = Release|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Release|arm64.ActiveCfg = Release|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Release|arm64.Build.0 = Release|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Release|x64.ActiveCfg = Release|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Release|x64.Build.0 = Release|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Release|x86.ActiveCfg = Release|Any CPU - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE}.Release|x86.Build.0 = Release|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Debug|arm64.ActiveCfg = Debug|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Debug|arm64.Build.0 = Debug|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Debug|x64.ActiveCfg = Debug|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Debug|x64.Build.0 = Debug|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Debug|x86.ActiveCfg = Debug|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Debug|x86.Build.0 = Debug|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Release|Any CPU.Build.0 = Release|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Release|arm64.ActiveCfg = Release|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Release|arm64.Build.0 = Release|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Release|x64.ActiveCfg = Release|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Release|x64.Build.0 = Release|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Release|x86.ActiveCfg = Release|Any CPU - {8034024A-2804-4920-921A-86ADCCBF0838}.Release|x86.Build.0 = Release|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Debug|arm64.ActiveCfg = Debug|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Debug|arm64.Build.0 = Debug|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Debug|x64.ActiveCfg = Debug|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Debug|x64.Build.0 = Debug|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Debug|x86.ActiveCfg = Debug|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Debug|x86.Build.0 = Debug|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Release|Any CPU.Build.0 = Release|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Release|arm64.ActiveCfg = Release|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Release|arm64.Build.0 = Release|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Release|x64.ActiveCfg = Release|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Release|x64.Build.0 = Release|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Release|x86.ActiveCfg = Release|Any CPU - {2A4B719C-4958-AD92-4A22-6472AAF98A37}.Release|x86.Build.0 = Release|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Debug|arm64.ActiveCfg = Debug|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Debug|arm64.Build.0 = Debug|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Debug|x64.ActiveCfg = Debug|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Debug|x64.Build.0 = Debug|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Debug|x86.ActiveCfg = Debug|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Debug|x86.Build.0 = Debug|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Release|Any CPU.Build.0 = Release|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Release|arm64.ActiveCfg = Release|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Release|arm64.Build.0 = Release|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Release|x64.ActiveCfg = Release|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Release|x64.Build.0 = Release|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Release|x86.ActiveCfg = Release|Any CPU - {B5B3101B-6638-418D-AE82-59984D9D6AC7}.Release|x86.Build.0 = Release|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Debug|arm64.ActiveCfg = Debug|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Debug|arm64.Build.0 = Debug|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Debug|x64.ActiveCfg = Debug|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Debug|x64.Build.0 = Debug|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Debug|x86.ActiveCfg = Debug|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Debug|x86.Build.0 = Debug|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Release|Any CPU.Build.0 = Release|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Release|arm64.ActiveCfg = Release|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Release|arm64.Build.0 = Release|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Release|x64.ActiveCfg = Release|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Release|x64.Build.0 = Release|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Release|x86.ActiveCfg = Release|Any CPU - {C4338ACD-DFDC-1724-6F35-36519B285D1A}.Release|x86.Build.0 = Release|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Debug|Any CPU.Build.0 = Debug|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Debug|arm64.ActiveCfg = Debug|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Debug|arm64.Build.0 = Debug|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Debug|x64.ActiveCfg = Debug|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Debug|x64.Build.0 = Debug|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Debug|x86.ActiveCfg = Debug|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Debug|x86.Build.0 = Debug|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Release|Any CPU.ActiveCfg = Release|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Release|Any CPU.Build.0 = Release|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Release|arm64.ActiveCfg = Release|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Release|arm64.Build.0 = Release|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Release|x64.ActiveCfg = Release|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Release|x64.Build.0 = Release|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Release|x86.ActiveCfg = Release|Any CPU - {928C9161-B7A5-41EA-991C-C4FB44605F97}.Release|x86.Build.0 = Release|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Debug|arm64.ActiveCfg = Debug|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Debug|arm64.Build.0 = Debug|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Debug|x64.ActiveCfg = Debug|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Debug|x64.Build.0 = Debug|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Debug|x86.ActiveCfg = Debug|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Debug|x86.Build.0 = Debug|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Release|Any CPU.Build.0 = Release|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Release|arm64.ActiveCfg = Release|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Release|arm64.Build.0 = Release|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Release|x64.ActiveCfg = Release|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Release|x64.Build.0 = Release|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Release|x86.ActiveCfg = Release|Any CPU - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {2ADE0A50-5012-4341-8F4F-97597C2D6920} = {2AE709FA-BE58-4287-BFD3-E80BEB605125} - {7ED6D69F-138F-40BD-9F37-3E4050E4D19B} = {2AE709FA-BE58-4287-BFD3-E80BEB605125} - {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684} = {2AE709FA-BE58-4287-BFD3-E80BEB605125} - {D9CF5BB9-12FC-CF7E-B415-ED924A3E87AE} = {2AE709FA-BE58-4287-BFD3-E80BEB605125} - {8034024A-2804-4920-921A-86ADCCBF0838} = {2AE709FA-BE58-4287-BFD3-E80BEB605125} - {2A4B719C-4958-AD92-4A22-6472AAF98A37} = {2AE709FA-BE58-4287-BFD3-E80BEB605125} - {B5B3101B-6638-418D-AE82-59984D9D6AC7} = {3F9A8B1C-5D2E-4A7F-9B8C-1E6D4F5A7B9C} - {C4338ACD-DFDC-1724-6F35-36519B285D1A} = {2AE709FA-BE58-4287-BFD3-E80BEB605125} - {928C9161-B7A5-41EA-991C-C4FB44605F97} = {2AE709FA-BE58-4287-BFD3-E80BEB605125} - {EEF549E7-18F7-4FAC-AB6D-8FB335DA5AE3} = {2AE709FA-BE58-4287-BFD3-E80BEB605125} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9326B58C-0AD3-4527-B3F4-86B54673C62E} - EndGlobalSection - GlobalSection(TestCaseManagementSettings) = postSolution - CategoryFile = ReactiveUI.vsmdi - EndGlobalSection -EndGlobal diff --git a/src/ReactiveUI.sln.DotSettings b/src/ReactiveUI.sln.DotSettings deleted file mode 100644 index 8699d2d9df..0000000000 --- a/src/ReactiveUI.sln.DotSettings +++ /dev/null @@ -1,8 +0,0 @@ - - True - C:\Users\Oren\Source\Git\ReactiveUI\RxUI.DotSettings - ..\RxUI.DotSettings - True - 1 - True - True \ No newline at end of file diff --git a/src/ReactiveUI.v3.ncrunchsolution b/src/ReactiveUI.v3.ncrunchsolution deleted file mode 100644 index 10420ac91d..0000000000 --- a/src/ReactiveUI.v3.ncrunchsolution +++ /dev/null @@ -1,6 +0,0 @@ - - - True - True - - \ No newline at end of file diff --git a/src/ReactiveUI/Activation/CanActivateViewFetcher.cs b/src/ReactiveUI/Activation/CanActivateViewFetcher.cs index 5973934d7c..7368dc0232 100644 --- a/src/ReactiveUI/Activation/CanActivateViewFetcher.cs +++ b/src/ReactiveUI/Activation/CanActivateViewFetcher.cs @@ -29,10 +29,6 @@ public int GetAffinityForView(Type view) => /// /// The view to observe. /// An observable tracking whether the view is active. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] -#endif public IObservable GetActivationForView(IActivatableView view) => view is not ICanActivate canActivate ? Observable.Return(false) diff --git a/src/ReactiveUI/Activation/IActivationForViewFetcher.cs b/src/ReactiveUI/Activation/IActivationForViewFetcher.cs index 0a5d181561..8e100274c8 100644 --- a/src/ReactiveUI/Activation/IActivationForViewFetcher.cs +++ b/src/ReactiveUI/Activation/IActivationForViewFetcher.cs @@ -55,9 +55,5 @@ public interface IActivationForViewFetcher /// /// The view to get the activation observable for. /// A Observable which will returns if Activation was successful. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] -#endif IObservable GetActivationForView(IActivatableView view); } diff --git a/src/ReactiveUI/Activation/ViewForMixins.cs b/src/ReactiveUI/Activation/ViewForMixins.cs index a387c814b2..c2202efc84 100644 --- a/src/ReactiveUI/Activation/ViewForMixins.cs +++ b/src/ReactiveUI/Activation/ViewForMixins.cs @@ -22,9 +22,7 @@ public static class ViewForMixins var score = x?.GetAffinityForView(t) ?? 0; return score > acc.count ? (score, x) : acc; }).viewFetcher, - RxApp.SmallCacheLimit); - - static ViewForMixins() => RxApp.EnsureInitialized(); + RxCacheSize.SmallCacheLimit); /// /// WhenActivated allows you to register a Func to be called when a @@ -99,10 +97,7 @@ public static void WhenActivated(this IActivatableViewModel item, Action /// A Disposable that deactivates this registration. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] - [RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IDisposable WhenActivated(this IActivatableView item, Func> block) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(item); @@ -126,10 +121,7 @@ public static IDisposable WhenActivated(this IActivatableView item, Func /// A Disposable that deactivates this registration. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] - [RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IDisposable WhenActivated(this IActivatableView item, Func> block, IViewFor? view) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(item); @@ -165,10 +157,7 @@ public static IDisposable WhenActivated(this IActivatableView item, Func /// A Disposable that deactivates this registration. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] - [RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IDisposable WhenActivated(this IActivatableView item, Action> block) => item.WhenActivated(block, null!); @@ -189,10 +178,7 @@ public static IDisposable WhenActivated(this IActivatableView item, Action /// A Disposable that deactivates this registration. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] - [RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IDisposable WhenActivated(this IActivatableView item, Action> block, IViewFor view) => // TODO: Create Test item.WhenActivated( () => @@ -219,10 +205,7 @@ public static IDisposable WhenActivated(this IActivatableView item, Action /// A Disposable that deactivates this registration. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] - [RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IDisposable WhenActivated(this IActivatableView item, Action block, IViewFor? view = null) => item.WhenActivated( () => @@ -233,6 +216,20 @@ public static IDisposable WhenActivated(this IActivatableView item, Action + /// Clears the activation fetcher cache. This method is intended for use by unit tests + /// to ensure the cache is invalidated when the service locator is reset. + /// + /// + /// WARNING: This method should ONLY be used in unit tests to reset cache state between test runs. + /// Never call this in production code as it will force re-querying of activation fetchers + /// from the service locator on the next access. + /// + internal static void ResetActivationFetcherCacheForTesting() + { + _activationFetcherCache.InvalidateAll(); + } + private static CompositeDisposable HandleViewActivation(Func> block, IObservable activation) { var viewDisposable = new SerialDisposable(); @@ -251,10 +248,7 @@ private static CompositeDisposable HandleViewActivation(Func activation) { var vmDisposable = new SerialDisposable(); diff --git a/src/ReactiveUI/Bindings/BindingTypeConverter.cs b/src/ReactiveUI/Bindings/BindingTypeConverter.cs new file mode 100644 index 0000000000..dcf6eda4cc --- /dev/null +++ b/src/ReactiveUI/Bindings/BindingTypeConverter.cs @@ -0,0 +1,103 @@ +// Copyright (c) 2025 .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; + +/// +/// Base class for type-pair binding converters. +/// +/// The source type to convert from. +/// The target type to convert to. +/// +/// This base class supplies the "type-only" metadata (/) and the +/// object-based shim (), allowing the dispatch +/// layer to avoid reflection. +/// +public abstract class BindingTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(TFrom); + + /// + public Type ToType => typeof(TTo); + + /// + /// Returns the affinity score for this converter. + /// + /// + /// A positive integer indicating converter priority. Higher values win when multiple converters match. + /// Return 0 if the converter cannot handle the type pair. + /// + /// + /// Affinity Guidelines: + /// + /// 0 - Cannot convert (no conversion possible) + /// 1 - Last resort converters (e.g., EqualityTypeConverter) + /// 2 - Standard ReactiveUI core converters (string, numeric, datetime) + /// 8 - Platform-specific standard converters (NSDate, WinForms controls) + /// 100+ - Third-party override range (use to override ReactiveUI defaults) + /// + /// + /// When multiple converters match the same type pair, the converter with the highest affinity is selected. + /// Third-party converters should return 100 or higher to override ReactiveUI defaults. + /// + /// + public abstract int GetAffinityForObjects(); + + /// + public abstract bool TryConvert(TFrom? from, object? conversionHint, [MaybeNullWhen(true)] out TTo? result); + + /// + public bool TryConvertTyped(object? from, object? conversionHint, out object? result) + { + // Allow null inputs for converters whose source type can represent null, and + // permit null outputs when the target type is nullable/reference. + TTo? typedResult; + if (from is null) + { + if (default(TFrom) is not null) + { + result = null; + return false; + } + + if (!TryConvert(default, conversionHint, out typedResult)) + { + result = null; + return false; + } + + if (typedResult is null && default(TTo) is not null) + { + result = null; + return false; + } + + result = typedResult; + return true; + } + + if (from is not TFrom castFrom) + { + result = null; + return false; + } + + if (!TryConvert(castFrom, conversionHint, out typedResult)) + { + result = null; + return false; + } + + if (typedResult is null && default(TTo) is not null) + { + result = null; + return false; + } + + result = typedResult; + return true; + } +} diff --git a/src/ReactiveUI/Bindings/BindingTypeConverterDispatch.cs b/src/ReactiveUI/Bindings/BindingTypeConverterDispatch.cs new file mode 100644 index 0000000000..9ebb248e5f --- /dev/null +++ b/src/ReactiveUI/Bindings/BindingTypeConverterDispatch.cs @@ -0,0 +1,160 @@ +// Copyright (c) 2025 .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; + +/// +/// Dispatches conversions using a type-only fast-path, avoiding reflection. +/// +internal static class BindingTypeConverterDispatch +{ + /// + /// Attempts conversion via the converter's type-only metadata ( and + /// ) and object shim (). + /// + /// The converter. + /// The source value. + /// The target type requested by the caller. + /// Implementation-defined hint. + /// The converted result. + /// if conversion succeeded; otherwise . + internal static bool TryConvert( + IBindingTypeConverter converter, + object? from, + Type toType, + object? conversionHint, + out object? result) + { + ArgumentExceptionHelper.ThrowIfNull(converter); + ArgumentExceptionHelper.ThrowIfNull(toType); + + if (converter.ToType != toType) + { + result = null; + return false; + } + + if (from is null) + { + var fromType = converter.FromType; + if (fromType.IsValueType && Nullable.GetUnderlyingType(fromType) is null) + { + result = null; + return false; + } + + return converter.TryConvertTyped(null, conversionHint, out result); + } + + var runtimeType = from.GetType(); + var converterFromType = converter.FromType; + + // Exact pair match keeps dispatch predictable and avoids assignability ambiguity, + // but allow nullable converters to accept boxed T values. + if (converterFromType != runtimeType && + Nullable.GetUnderlyingType(converterFromType) != runtimeType) + { + result = null; + return false; + } + + return converter.TryConvertTyped(from, conversionHint, out result); + } + + /// + /// Attempts conversion using a fallback converter. + /// + /// The fallback converter. + /// The source runtime type. + /// The source value (guaranteed non-null by caller). + /// The target type. + /// Implementation-defined hint. + /// The converted result. + /// if conversion succeeded; otherwise, . + internal static bool TryConvertFallback( + IBindingFallbackConverter converter, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, + object from, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType, + object? conversionHint, + out object? result) + { + ArgumentExceptionHelper.ThrowIfNull(converter); + ArgumentExceptionHelper.ThrowIfNull(from); + ArgumentExceptionHelper.ThrowIfNull(toType); + + // Delegate to fallback converter (from is guaranteed non-null) + if (!converter.TryConvert(fromType, from, toType, conversionHint, out result)) + { + result = null; + return false; + } + + // Fallback converters must still guarantee a non-null result on success. + if (result is null) + { + result = null; + return false; + } + + return true; + } + + /// + /// Unified dispatch method that handles both typed and fallback converters. + /// + /// The converter (either or ). + /// The source runtime type. + /// The source value. + /// The target type. + /// Implementation-defined hint. + /// The converted result. + /// if conversion succeeded; otherwise, . + /// + /// This method automatically dispatches to the appropriate converter type: + /// + /// - uses exact pair matching + /// - requires non-null input + /// + /// + internal static bool TryConvertAny( + object? converter, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, + object? from, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType, + object? conversionHint, + out object? result) + { + ArgumentExceptionHelper.ThrowIfNull(toType); + + if (converter is null) + { + result = null; + return false; + } + + // Dispatch to typed converter + if (converter is IBindingTypeConverter typedConverter) + { + return TryConvert(typedConverter, from, toType, conversionHint, out result); + } + + // Dispatch to fallback converter (requires non-null input) + if (converter is IBindingFallbackConverter fallbackConverter) + { + if (from is null) + { + result = null; + return false; + } + + return TryConvertFallback(fallbackConverter, fromType, from, toType, conversionHint, out result); + } + + // Unknown converter type + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Command/CommandBinder.cs b/src/ReactiveUI/Bindings/Command/CommandBinder.cs index 51a34c7abf..8a5c3b72c2 100644 --- a/src/ReactiveUI/Bindings/Command/CommandBinder.cs +++ b/src/ReactiveUI/Bindings/Command/CommandBinder.cs @@ -16,7 +16,6 @@ public static class CommandBinder static CommandBinder() { - RxApp.EnsureInitialized(); _binderImplementation = AppLocator.Current.GetService() ?? new CommandBinderImplementation(); @@ -31,23 +30,25 @@ static CommandBinder() /// The property type. /// The control type. /// The parameter type. - /// A class representing the binding. Dispose it to disconnect - /// the binding. + /// + /// A class representing the binding. Dispose it to disconnect the binding. + /// /// The View. /// The View model. /// The ViewModel command to bind. /// The name of the control on the view. - /// The ViewModel property to pass as the - /// param of the ICommand. - /// If specified, bind to the specific event - /// instead of the default. - /// NOTE: If this parameter is used inside WhenActivated, it's - /// important to dispose the binding when the view is deactivated. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - public static IReactiveBinding BindCommand( + /// The ViewModel property to pass as the param of the ICommand. + /// + /// If specified, bind to the specific event instead of the default. + /// NOTE: If this parameter is used inside WhenActivated, it's important to dispose the binding when the view is deactivated. + /// + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static IReactiveBinding BindCommand< + TView, + TViewModel, + TProp, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] TControl, + TParam>( this TView view, TViewModel? viewModel, Expression> propertyName, @@ -56,8 +57,16 @@ public static IReactiveBinding BindCommand - _binderImplementation.BindCommand(viewModel, view, propertyName, controlName, withParameter, toEvent); + where TProp : ICommand + where TControl : class + { + ArgumentExceptionHelper.ThrowIfNull(view); + ArgumentExceptionHelper.ThrowIfNull(propertyName); + ArgumentExceptionHelper.ThrowIfNull(controlName); + ArgumentExceptionHelper.ThrowIfNull(withParameter); + + return _binderImplementation.BindCommand(viewModel, view, propertyName, controlName, withParameter, toEvent); + } /// /// Bind a command from the ViewModel to an explicitly specified control @@ -67,21 +76,23 @@ public static IReactiveBinding BindCommandThe view model type. /// The property type. /// The control type. - /// A class representing the binding. Dispose it to disconnect - /// the binding. + /// + /// A class representing the binding. Dispose it to disconnect the binding. + /// /// The View. /// The View model. /// The ViewModel command to bind. /// The name of the control on the view. - /// If specified, bind to the specific event - /// instead of the default. - /// NOTE: If this parameter is used inside WhenActivated, it's - /// important to dispose the binding when the view is deactivated. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - public static IReactiveBinding BindCommand( + /// + /// If specified, bind to the specific event instead of the default. + /// NOTE: If this parameter is used inside WhenActivated, it's important to dispose the binding when the view is deactivated. + /// + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static IReactiveBinding BindCommand< + TView, + TViewModel, + TProp, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] TControl>( this TView view, TViewModel? viewModel, Expression> propertyName, @@ -89,8 +100,15 @@ public static IReactiveBinding BindCommand - _binderImplementation.BindCommand(viewModel, view, propertyName, controlName, toEvent); + where TProp : ICommand + where TControl : class + { + ArgumentExceptionHelper.ThrowIfNull(view); + ArgumentExceptionHelper.ThrowIfNull(propertyName); + ArgumentExceptionHelper.ThrowIfNull(controlName); + + return _binderImplementation.BindCommand(viewModel, view, propertyName, controlName, toEvent); + } /// /// Bind a command from the ViewModel to an explicitly specified control @@ -101,41 +119,41 @@ public static IReactiveBinding BindCommandThe property type. /// The control type. /// The parameter type. - /// A class representing the binding. Dispose it to disconnect - /// the binding. + /// + /// A class representing the binding. Dispose it to disconnect the binding. + /// /// The View. /// The View model. /// The ViewModel command to bind. /// The name of the control on the view. - /// The ViewModel property to pass as the - /// param of the ICommand. - /// If specified, bind to the specific event - /// instead of the default. - /// NOTE: If this parameter is used inside WhenActivated, it's - /// important to dispose the binding when the view is deactivated. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - public static IReactiveBinding BindCommand( - this TView view, - TViewModel? viewModel, - Expression> propertyName, - Expression> controlName, - Expression> withParameter, - string? toEvent = null) + /// The ViewModel property to pass as the param of the ICommand. + /// + /// If specified, bind to the specific event instead of the default. + /// NOTE: If this parameter is used inside WhenActivated, it's important to dispose the binding when the view is deactivated. + /// + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static IReactiveBinding BindCommand< + TView, + TViewModel, + TProp, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] TControl, + TParam>( + this TView view, + TViewModel? viewModel, + Expression> propertyName, + Expression> controlName, + Expression> withParameter, + string? toEvent = null) where TView : class, IViewFor where TViewModel : class where TProp : ICommand + where TControl : class { ArgumentExceptionHelper.ThrowIfNull(view); + ArgumentExceptionHelper.ThrowIfNull(propertyName); + ArgumentExceptionHelper.ThrowIfNull(controlName); + ArgumentExceptionHelper.ThrowIfNull(withParameter); - return _binderImplementation.BindCommand( - viewModel, - view, - propertyName, - controlName, - withParameter, - toEvent); + return _binderImplementation.BindCommand(viewModel, view, propertyName, controlName, withParameter, toEvent); } } diff --git a/src/ReactiveUI/Bindings/Command/CommandBinderImplementation.cs b/src/ReactiveUI/Bindings/Command/CommandBinderImplementation.cs index 3e4aa107fe..fd16f04847 100644 --- a/src/ReactiveUI/Bindings/Command/CommandBinderImplementation.cs +++ b/src/ReactiveUI/Bindings/Command/CommandBinderImplementation.cs @@ -8,168 +8,228 @@ namespace ReactiveUI; /// -/// Used by the CommandBinder extension methods to handle binding View controls and ViewModel commands. +/// Implements command binding for extension methods by wiring ViewModel +/// instances to view controls and keeping the binding up to date as the command +/// and/or control instance changes. /// +/// +/// +/// This implementation uses expression rewriting and dynamic observation (via WhenAny* infrastructure) +/// to locate and track members described by expression trees. +/// +/// +/// For trimming/AOT: the public binding entry points are annotated because they may require reflection over +/// members that are not statically visible to the trimmer, and may require dynamic code paths depending on +/// platform/runtime. +/// +/// public class CommandBinderImplementation : ICommandBinderImplementation { /// - /// Bind a command from the ViewModel to an explicitly specified control - /// on the View. + /// Binds a command from the ViewModel to an explicitly specified control on the view. /// /// The view type. /// The view model type. - /// The property type. + /// The property type of the command. /// The control type. /// The parameter type. - /// The View model. - /// The View. - /// The ViewModel command to bind. - /// The name of the control on the view. - /// The ViewModel property to pass as the - /// param of the ICommand. - /// If specified, bind to the specific event - /// instead of the default. - /// NOTE: If this parameter is used inside WhenActivated, it's - /// important to dispose the binding when the view is deactivated. - /// - /// A class representing the binding. Dispose it to disconnect - /// the binding. - /// - /// nameof(vmProperty) - /// or - /// nameof(vmProperty). -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - public IReactiveBinding BindCommand( - TViewModel? viewModel, - TView view, - Expression> vmProperty, - Expression> controlProperty, - Expression> withParameter, - string? toEvent = null) + /// The view model instance. + /// The view instance. + /// An expression selecting the ViewModel command to bind. + /// An expression selecting the control on the view. + /// An expression selecting the ViewModel property to pass as the command parameter. + /// + /// If specified, binds to the given event instead of the default command event. + /// If used inside WhenActivated, ensure the returned binding is disposed when the view deactivates. + /// + /// An representing the binding; dispose it to disconnect. + /// + /// Thrown when or is . + /// + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public IReactiveBinding BindCommand< + TView, + TViewModel, + TProp, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] TControl, + TParam>( + TViewModel? viewModel, + TView view, + Expression> vmProperty, + Expression> controlProperty, + Expression> withParameter, + string? toEvent = null) where TView : class, IViewFor where TViewModel : class where TProp : ICommand + where TControl : class { ArgumentExceptionHelper.ThrowIfNull(vmProperty); - ArgumentExceptionHelper.ThrowIfNull(controlProperty); var vmExpression = Reflection.Rewrite(vmProperty.Body); var controlExpression = Reflection.Rewrite(controlProperty.Body); + var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Cast(); - var bindingDisposable = BindCommandInternal(source, view, controlExpression, withParameter.ToObservable(viewModel), toEvent); + var bindingDisposable = BindCommandInternal( + source, + view, + controlExpression, + withParameter.ToObservable(viewModel), + toEvent); return new ReactiveBinding( - view, - controlExpression, - vmExpression, - source, - BindingDirection.OneWay, - bindingDisposable); + view, + controlExpression, + vmExpression, + source, + BindingDirection.OneWay, + bindingDisposable); } /// - /// Bind a command from the ViewModel to an explicitly specified control - /// on the View. + /// Binds a command from the ViewModel to an explicitly specified control on the view. /// /// The view type. /// The view model type. - /// The property type. + /// The property type of the command. /// The control type. /// The parameter type. - /// A class representing the binding. Dispose it to disconnect - /// the binding. - /// The View model. - /// The View. - /// The ViewModel command to bind. - /// The name of the control on the view. - /// The ViewModel property to pass as the - /// param of the ICommand. - /// If specified, bind to the specific event - /// instead of the default. - /// NOTE: If this parameter is used inside WhenActivated, it's - /// important to dispose the binding when the view is deactivated. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - public IReactiveBinding BindCommand( - TViewModel? viewModel, - TView view, - Expression> vmProperty, - Expression> controlProperty, - IObservable withParameter, - string? toEvent = null) + /// The view model instance. + /// The view instance. + /// An expression selecting the ViewModel command to bind. + /// An expression selecting the control on the view. + /// An observable providing values to pass as the command parameter. + /// + /// If specified, binds to the given event instead of the default command event. + /// If used inside WhenActivated, ensure the returned binding is disposed when the view deactivates. + /// + /// An representing the binding; dispose it to disconnect. + /// + /// Thrown when or is . + /// + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public IReactiveBinding BindCommand< + TView, + TViewModel, + TProp, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] TControl, + TParam>( + TViewModel? viewModel, + TView view, + Expression> vmProperty, + Expression> controlProperty, + IObservable withParameter, + string? toEvent = null) where TView : class, IViewFor where TViewModel : class where TProp : ICommand + where TControl : class { ArgumentExceptionHelper.ThrowIfNull(vmProperty); - ArgumentExceptionHelper.ThrowIfNull(controlProperty); var vmExpression = Reflection.Rewrite(vmProperty.Body); var controlExpression = Reflection.Rewrite(controlProperty.Body); + var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Cast(); - var bindingDisposable = BindCommandInternal(source, view, controlExpression, withParameter, toEvent); + var bindingDisposable = BindCommandInternal( + source, + view, + controlExpression, + withParameter, + toEvent); return new ReactiveBinding( - view, - controlExpression, - vmExpression, - source, - BindingDirection.OneWay, - bindingDisposable); + view, + controlExpression, + vmExpression, + source, + BindingDirection.OneWay, + bindingDisposable); } -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - private static IDisposable BindCommandInternal( - IObservable source, - TView view, - Expression controlExpression, - IObservable withParameter, - string? toEvent) + /// + /// Wires the current command/control pair to an binding, and updates that wiring + /// whenever the command instance or control instance changes. + /// + /// The view type. + /// The command type. + /// The parameter type. + /// The control type. + /// Observable producing command instances. + /// The view instance used to observe the control expression chain. + /// The rewritten expression identifying the control on the view. + /// Observable producing command parameter values. + /// Optional event name override for the binding. + /// + /// A disposable that tears down the observation subscription and the most-recently-created command binding. + /// + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + private static IDisposable BindCommandInternal< + TView, + TProp, + TParam, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] TControl>( + IObservable source, + TView view, + Expression controlExpression, + IObservable withParameter, + string? toEvent) where TView : class, IViewFor where TProp : ICommand + where TControl : class { - var disposable = Disposable.Empty; - - var bindInfo = source.CombineLatest( - view.SubscribeToExpressionChain(controlExpression, false, false, RxApp.SuppressViewCommandBindingMessage).Select(x => x.GetValue()), - (val, host) => new { val, host }); + // SerialDisposable safely replaces and disposes the previous binding when a new one is assigned. + var currentBinding = new SerialDisposable(); + + // Cache boxing of parameter values once to avoid rebuilding the Select pipeline on every rebind. + var boxedParameter = withParameter.Select(static p => (object?)p); + + // Observe the control expression chain and extract the current control instance. + var controlValues = + view.SubscribeToExpressionChain( + controlExpression, + beforeChange: false, + skipInitial: false, + suppressWarnings: false) + .Select(static x => x.GetValue()); + + // CombineLatest ensures rebinding occurs when either the command or control changes. + // ValueTuple avoids per-notification heap allocations. + var bindInfo = source.CombineLatest(controlValues, static (command, host) => (command, host)); + + var subscription = bindInfo.Subscribe(tuple => + { + var (command, host) = tuple; - var propSub = bindInfo - .Subscribe(x => + // Preserve existing behavior: if the control is currently null, + // do not tear down or recreate the existing binding. + if (host is null) { - if (x.host is null) - { - return; - } - - disposable.Dispose(); - if (x is null) - { - disposable = Disposable.Empty; - return; - } - - disposable = !string.IsNullOrEmpty(toEvent) ? - CreatesCommandBinding.BindCommandToObject(x.val, x.host, withParameter.Select(y => (object)y!), toEvent) : - CreatesCommandBinding.BindCommandToObject(x.val, x.host, withParameter.Select(y => (object)y!)); - }); - - return Disposable.Create(() => - { - propSub.Dispose(); - disposable.Dispose(); + return; + } + + // Match original semantics: allow null if the cast fails. + var control = host as TControl; + + // Assigning to SerialDisposable disposes the previous binding deterministically. + currentBinding.Disposable = + !string.IsNullOrEmpty(toEvent) + ? CreatesCommandBinding.BindCommandToObject( + command, + control, + boxedParameter, + toEvent!) + : CreatesCommandBinding.BindCommandToObject( + command, + control, + boxedParameter); }); + + // Dispose ordering: stop producing new bindings first, then dispose the active binding. + return new CompositeDisposable(subscription, currentBinding); } } diff --git a/src/ReactiveUI/Bindings/Command/CommandBinderImplementationMixins.cs b/src/ReactiveUI/Bindings/Command/CommandBinderImplementationMixins.cs index ccfefbcf42..dbd3b12e98 100644 --- a/src/ReactiveUI/Bindings/Command/CommandBinderImplementationMixins.cs +++ b/src/ReactiveUI/Bindings/Command/CommandBinderImplementationMixins.cs @@ -12,26 +12,59 @@ namespace ReactiveUI; /// internal static class CommandBinderImplementationMixins { -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("BindCommand may reference types that could be trimmed")] - [RequiresDynamicCode("BindCommand uses reflection which requires dynamic code generation")] -#endif - public static IReactiveBinding BindCommand( + /// + /// Binds a command on the ViewModel to a control on the View (no explicit event name). + /// + /// The view type. + /// The view model type. + /// The command property type. + /// The control type. + /// The command binder implementation. + /// The view model instance. + /// The view instance. + /// Expression selecting the command property on the view model. + /// Expression selecting the control on the view. + /// Optional event name on the control that will trigger the command. + /// A reactive binding representing the command binding. + /// Thrown when , , , or is . + /// + /// Trimming note: requires only public properties on the control type. + /// + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static IReactiveBinding BindCommand( this ICommandBinderImplementation @this, TViewModel? viewModel, TView view, Expression> propertyName, Expression> controlName, string? toEvent = null) - where TView : class, IViewFor - where TViewModel : class - where TProp : ICommand => + where TView : class, IViewFor + where TViewModel : class + where TProp : ICommand + where TControl : class => @this.BindCommand(viewModel, view, propertyName, controlName, Observable.Empty, toEvent); -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("BindCommand may reference types that could be trimmed")] - [RequiresDynamicCode("BindCommand uses reflection which requires dynamic code generation")] -#endif + /// + /// Binds a command on the ViewModel to a control on the View (optional explicit event name). + /// + /// The view type. + /// The view model type. + /// The command property type. + /// The control type. + /// The command parameter type. + /// The command binder implementation. + /// The view model instance. + /// The view instance. + /// Expression selecting the command property on the view model. + /// Expression selecting the control on the view. + /// Expression selecting a command parameter value from the view model. + /// Optional event name on the control that will trigger the command. + /// A reactive binding representing the command binding. + /// Thrown when , , , or is . + /// + /// Trimming note: if is specified, implementations may reflect over public events. + /// + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static IReactiveBinding BindCommand( this ICommandBinderImplementation @this, TViewModel? viewModel, @@ -43,6 +76,7 @@ public static IReactiveBinding BindCommand +/// AOT-compatible command binding helper that uses generic type parameters instead of reflection. +/// internal static class CreatesCommandBinding { - private static readonly MemoizingMRUCache _bindCommandCache = - new( - (t, _) => AppLocator.Current.GetServices() - .Aggregate((score: 0, binding: (ICreatesCommandBinding?)null), (acc, x) => - { - var score = x.GetAffinityForObject(t, false); - return (score > acc.score) ? (score, x) : acc; - }).binding, - RxApp.SmallCacheLimit); - - private static readonly MemoizingMRUCache _bindCommandEventCache = - new( - (t, _) => AppLocator.Current.GetServices() - .Aggregate((score: 0, binding: (ICreatesCommandBinding?)null), (acc, x) => - { - var score = x.GetAffinityForObject(t, true); - return (score > acc.score) ? (score, x) : acc; - }).binding, - RxApp.SmallCacheLimit); + /// + /// Binds a command to a control using default event discovery. Fully AOT-compatible. + /// + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public static IDisposable BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] TControl>(ICommand? command, TControl? target, IObservable commandParameter) + where TControl : class + { + var binder = GetBinder(hasEventTarget: false); + var ret = binder.BindCommandToObject(command, target, commandParameter) + ?? throw new Exception($"Couldn't bind Command Binder for {typeof(TControl).FullName}"); + return ret; + } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindCommandToObject uses reflection and generic method instantiation")] - [RequiresUnreferencedCode("BindCommandToObject may reference members that could be trimmed")] -#endif - public static IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter) + /// + /// Binds a command to a control using a specific event. Fully AOT-compatible. + /// + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public static IDisposable BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] TControl, TEventArgs>( + ICommand? command, + TControl? target, + IObservable commandParameter, + string eventName) + where TControl : class { - var type = target!.GetType(); - var binder = _bindCommandCache.Get(type) ?? throw new Exception($"Couldn't find a Command Binder for {type.FullName}"); - var ret = binder.BindCommandToObject(command, target, commandParameter) ?? throw new Exception($"Couldn't bind Command Binder for {type.FullName}"); + var binder = GetBinder(hasEventTarget: true); + var ret = binder.BindCommandToObject(command, target, commandParameter, eventName) + ?? throw new Exception($"Couldn't bind Command Binder for {typeof(TControl).FullName} and event {eventName}"); return ret; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindCommandToObject uses reflection and generic method instantiation")] - [RequiresUnreferencedCode("BindCommandToObject may reference members that could be trimmed")] -#endif - public static IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter, string? eventName) + private static ICreatesCommandBinding GetBinder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>(bool hasEventTarget) { - var type = target!.GetType(); - var binder = _bindCommandEventCache.Get(type) ?? throw new Exception($"Couldn't find an Event Binder for {type.FullName} and event {eventName}"); - var eventArgsType = Reflection.GetEventArgsTypeForEvent(type, eventName); - var mi = binder.GetType().GetTypeInfo().DeclaredMethods.First(static x => x.Name == "BindCommandToObject" && x.IsGenericMethod); - mi = mi.MakeGenericMethod(eventArgsType); + var binder = AppLocator.Current.GetServices() + .Aggregate((score: 0, binding: (ICreatesCommandBinding?)null), (acc, x) => + { + var score = x.GetAffinityForObject(hasEventTarget); + return (score > acc.score) ? (score, x) : acc; + }).binding; - var ret = (IDisposable)mi.Invoke(binder, [command, target, commandParameter, eventName])! ?? throw new Exception($"Couldn't bind Command Binder for {type.FullName} and event {eventName}"); - return ret; + return binder ?? throw new Exception($"Couldn't find a Command Binder for {typeof(T).FullName}"); } } diff --git a/src/ReactiveUI/Bindings/Command/CreatesCommandBindingViaCommandParameter.cs b/src/ReactiveUI/Bindings/Command/CreatesCommandBindingViaCommandParameter.cs index 61cd392b74..4182e470aa 100644 --- a/src/ReactiveUI/Bindings/Command/CreatesCommandBindingViaCommandParameter.cs +++ b/src/ReactiveUI/Bindings/Command/CreatesCommandBindingViaCommandParameter.cs @@ -4,110 +4,226 @@ // See the LICENSE file in the project root for full license information. using System.Reflection; +using System.Runtime.CompilerServices; using System.Windows.Input; -namespace ReactiveUI; - -/// -/// Class that registers Command Binding and Command Parameter Binding. -/// -public class CreatesCommandBindingViaCommandParameter : ICreatesCommandBinding +namespace ReactiveUI { - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Property access requires dynamic code generation")] - [RequiresUnreferencedCode("Property access may reference members that could be trimmed")] -#endif - public int GetAffinityForObject(Type type, bool hasEventTarget) + /// + /// Creates command bindings for objects that expose Command and CommandParameter + /// as public instance properties. + /// + /// + /// + /// This binder targets command-source style controls (for example, WPF-style controls) where command execution + /// is driven by setting properties rather than subscribing to an event. + /// + /// + /// Trimming/AOT note: This type uses name-based reflection to locate public properties. Consumers running under + /// trimming must ensure the relevant public properties are preserved on the target control types. This + /// requirement is expressed via on the public generic entry points. + /// + /// + /// Performance note: This implementation uses a per-closed-generic static cache (“holder”) rather than a global MRU. + /// Steady-state access is lock-free and reduces lookup overhead to static field reads. + /// + /// + public sealed class CreatesCommandBindingViaCommandParameter : ICreatesCommandBinding { - if (hasEventTarget) + /// + /// The expected name of the command property. + /// + private const string CommandPropertyName = "Command"; + + /// + /// The expected name of the command parameter property. + /// + private const string CommandParameterPropertyName = "CommandParameter"; + + /// + /// + /// If an explicit event target exists, this binder is not applicable and returns 0. + /// Otherwise, it returns 5 if the target type exposes the required public instance properties; otherwise it returns 0. + /// + public int GetAffinityForObject< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>(bool hasEventTarget) { - return 0; + if (hasEventTarget) + { + return 0; + } + + return Holder.HasRequiredProperties ? 5 : 0; } - var propsToFind = new[] + /// + /// + /// + /// This implementation is intentionally “best effort.” If required properties cannot be resolved for + /// , it returns to preserve legacy behavior where binder + /// selection is expected to be affinity-driven rather than exception-driven. + /// + /// + /// Disposal ordering minimizes observable races: the parameter subscription is disposed before restoring + /// the original parameter value. + /// + /// + /// The command property is set after establishing the parameter subscription, preserving historical ordering + /// semantics (“set Command last”). + /// + /// + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T>( + ICommand? command, + T? target, + IObservable commandParameter) + where T : class { - new { Name = "Command", TargetType = typeof(ICommand) }, - new { Name = "CommandParameter", TargetType = typeof(object) }, - }; + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(commandParameter); - return propsToFind.All(x => - { - var pi = type.GetRuntimeProperty(x.Name); - return pi is not null; - }) ? 5 : 0; - } + var commandProperty = Holder.CommandProperty; + var commandParameterProperty = Holder.CommandParameterProperty; - /// -#if NET6_0_OR_GREATER - public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>( - bool hasEventTarget) -#else - public int GetAffinityForObject( - bool hasEventTarget) -#endif - { - if (hasEventTarget) - { - return 0; + // Preserve historical behavior: silently no-op if properties are missing. + if (commandProperty is null || commandParameterProperty is null) + { + return Disposable.Empty; + } + + // Snapshot original values once. Restoration occurs on disposal. + var originalCommand = commandProperty.GetValue(target); + var originalParameter = commandParameterProperty.GetValue(target); + + // Subscribe first so dispose can stop updates before restoration. + // This delegate intentionally remains simple and allocation-minimal (no LINQ). + var subscription = commandParameter.Subscribe( + value => commandParameterProperty.SetValue(target, value)); + + // Set command last to preserve ordering semantics. + commandProperty.SetValue(target, command); + + // Use Rx disposable to unbind and restore original values. AnonymousDisposable is idempotent and thread-safe. + return Disposable.Create(() => + { + // Stop parameter updates first. + subscription.Dispose(); + + // Restore original values in a predictable order. + commandParameterProperty.SetValue(target, originalParameter); + commandProperty.SetValue(target, originalCommand); + }); } - var propsToFind = new[] - { - new { Name = "Command", TargetType = typeof(ICommand) }, - new { Name = "CommandParameter", TargetType = typeof(object) }, - }; + /// + /// + /// This binder is for command-property based binding. If an event name is specified, event-based binders + /// should be used. This method therefore returns . + /// + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject( + ICommand? command, + T? target, + IObservable commandParameter, + string eventName) + where T : class => Disposable.Empty; + + /// + /// + /// This binder is for command-property based binding. If an event name is specified, event-based binders + /// should be used. This method therefore returns . + /// + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(ICommand? command, T? target, IObservable commandParameter, Action> addHandler, Action> removeHandler) + where T : class + where TEventArgs : EventArgs => Disposable.Empty; - return propsToFind.All(static x => + /// + /// Per-closed-generic cache of resolved command properties for a target type . + /// + /// + /// The target type. Public properties must be preserved in trimmed applications. + /// + /// + /// + /// This pattern avoids a global type-keyed cache that performs reflection in a cache factory delegate, + /// which is a frequent source of trimming warnings and hard-to-annotate member requirements. + /// + /// + /// Static initialization is thread-safe by the CLR. After initialization, access is lock-free. + /// + /// + private static class Holder< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T> { - var pi = typeof(T).GetRuntimeProperty(x.Name); - return pi is not null; - }) ? 5 : 0; - } + /// + /// Gets a value indicating whether the target type exposes both required properties. + /// + internal static readonly bool HasRequiredProperties; - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindCommandToObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("BindCommandToObject uses methods that may require unreferenced code")] -#endif - public IDisposable? BindCommandToObject(ICommand? command, object? target, IObservable commandParameter) - { - ArgumentExceptionHelper.ThrowIfNull(target); + /// + /// Gets the resolved public instance property named Command, or if missing. + /// + internal static readonly PropertyInfo? CommandProperty; - var type = target!.GetType(); - var cmdPi = type.GetRuntimeProperty("Command"); - var cmdParamPi = type.GetRuntimeProperty("CommandParameter"); - var ret = new CompositeDisposable(); + /// + /// Gets the resolved public instance property named CommandParameter, or if missing. + /// + internal static readonly PropertyInfo? CommandParameterProperty; - var originalCmd = cmdPi?.GetValue(target, null); - var originalCmdParam = cmdParamPi?.GetValue(target, null); + /// + /// Initializes static members of the class. + /// + static Holder() + { + ResolveProperties(typeof(T), out CommandProperty, out CommandParameterProperty); + HasRequiredProperties = CommandProperty is not null && CommandParameterProperty is not null; + } - ret.Add(Disposable.Create(() => - { - cmdPi?.SetValue(target, originalCmd, null); - cmdParamPi?.SetValue(target, originalCmdParam, null); - })); + /// + /// Resolves required properties via a single pass over public instance properties. + /// + /// The target type to inspect. + /// Receives the resolved Command property, if present. + /// Receives the resolved CommandParameter property, if present. + /// + /// The method avoids LINQ and repeated reflection calls to reduce overhead. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ResolveProperties( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, + out PropertyInfo? command, + out PropertyInfo? commandParameter) + { + command = null; + commandParameter = null; - ret.Add(commandParameter.Subscribe(x => cmdParamPi?.SetValue(target, x, null))); - cmdPi?.SetValue(target, command, null); + var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); - return ret; - } + for (var i = 0; i < properties.Length; i++) + { + var p = properties[i]; + var name = p.Name; - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindCommandToObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("BindCommandToObject uses methods that may require unreferenced code")] -#endif - public IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter, string eventName) -#if MONO - where TEventArgs : EventArgs -#endif - { - // NB: We should fall back to the generic Event-based handler if - // an event target is specified -#pragma warning disable IDE0022 // Use expression body for methods - return Disposable.Empty; -#pragma warning restore IDE0022 // Use expression body for methods + if (command is null && + string.Equals(name, CommandPropertyName, StringComparison.Ordinal)) + { + command = p; + continue; + } + + if (commandParameter is null && + string.Equals(name, CommandParameterPropertyName, StringComparison.Ordinal)) + { + commandParameter = p; + } + + if (command is not null && commandParameter is not null) + { + return; + } + } + } + } } } diff --git a/src/ReactiveUI/Bindings/Command/CreatesCommandBindingViaEvent.cs b/src/ReactiveUI/Bindings/Command/CreatesCommandBindingViaEvent.cs index c03bef4a7d..d587e45f58 100644 --- a/src/ReactiveUI/Bindings/Command/CreatesCommandBindingViaEvent.cs +++ b/src/ReactiveUI/Bindings/Command/CreatesCommandBindingViaEvent.cs @@ -4,17 +4,33 @@ // See the LICENSE file in the project root for full license information. using System.Reflection; +using System.Runtime.CompilerServices; using System.Windows.Input; namespace ReactiveUI; /// -/// This binder is the default binder for connecting to arbitrary events. +/// Default command binder that connects an to an event on a target object. /// -public class CreatesCommandBindingViaEvent : ICreatesCommandBinding +/// +/// +/// This binder supports a small set of conventional "default" events (for example, Click), +/// and can also bind to an explicitly named event. +/// +/// +/// Reflection-based event lookup and string-based event subscription are not trimming/AOT-safe in general. +/// Use the generic overloads with explicit with the add/remove handler delegates to avoid the reflection cost. +/// +/// +public sealed class CreatesCommandBindingViaEvent : ICreatesCommandBinding { - // NB: These are in priority order - private static readonly List<(string name, Type type)> _defaultEventsToBind = + /// + /// Default events to attempt, in priority order. + /// + /// + /// The first event found on the target type is used. + /// + private static readonly (string Name, Type ArgsType)[] DefaultEventsToBind = [ ("Click", typeof(EventArgs)), ("TouchUpInside", typeof(EventArgs)), @@ -22,90 +38,268 @@ public class CreatesCommandBindingViaEvent : ICreatesCommandBinding ]; /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Event binding requires dynamic code generation")] - [RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] -#endif - public int GetAffinityForObject(Type type, bool hasEventTarget) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetAffinityForObject< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)]T>(bool hasEventTarget) { if (hasEventTarget) { return 5; } - return _defaultEventsToBind.Any(x => - { - var ei = type.GetRuntimeEvent(x.name); - return ei is not null; - }) ? 3 : 0; + // Fast, allocation-free per-closed-generic cache. + return DefaultEventCache.HasDefaultEvent ? 3 : 0; } - /// -#if NET6_0_OR_GREATER - public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>( - bool hasEventTarget) -#else - public int GetAffinityForObject( - bool hasEventTarget) -#endif + /// + /// Binds a command to the default event on a target object using a generic type parameter. + /// + /// The type of the target object. + /// The command to bind. If , no binding is created. + /// The target object. + /// An observable that supplies command parameter values. + /// A disposable that unbinds the command. + /// Thrown when is . + /// + /// Thrown when no default event exists on and the caller did not specify an event explicitly. + /// + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T>( + ICommand? command, + T? target, + IObservable commandParameter) + where T : class { - if (hasEventTarget) + ArgumentExceptionHelper.ThrowIfNull(target); + + // Typical binding semantics: null command => no-op binding. + if (command is null) { - return 5; + return Disposable.Empty; } - return _defaultEventsToBind.Any(static x => + var eventName = DefaultEventCache.DefaultEventName; + if (eventName is null) { - var ei = typeof(T).GetRuntimeEvent(x.name); - return ei is not null; - }) ? 3 : 0; + throw new InvalidOperationException( + $"Couldn't find a default event to bind to on {typeof(T).FullName}, specify an event explicitly"); + } + + // Default events in this binder are EventArgs-shaped. + return BindCommandToObject(command, target, commandParameter, eventName); } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Event binding requires dynamic code generation and reflection")] - [RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] -#endif - public IDisposable? BindCommandToObject(ICommand? command, object? target, IObservable commandParameter) + /// + /// Binds a command to a specific event on a target object using generic type parameters. + /// + /// The type of the target object. + /// The event arguments type. + /// The command to bind. If , no binding is created. + /// The target object. + /// An observable that supplies command parameter values. + /// The name of the event to bind to. + /// A disposable that unbinds the command. + /// + /// Thrown when or is . + /// + /// + /// Thrown when is empty. + /// + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject( + ICommand? command, + T? target, + IObservable commandParameter, + string eventName) + where T : class { ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(eventName); - var type = target!.GetType(); - var eventInfo = _defaultEventsToBind - .Select(x => new { EventInfo = type.GetRuntimeEvent(x.name), Args = x.type }) - .FirstOrDefault(x => x.EventInfo is not null) ?? throw new Exception( - $"Couldn't find a default event to bind to on {target.GetType().FullName}, specify an event explicitly"); - var mi = GetType().GetRuntimeMethods().First(x => x.Name == "BindCommandToObject" && x.IsGenericMethod); - mi = mi.MakeGenericMethod(eventInfo.Args); + if (eventName.Length == 0) + { + throw new ArgumentException("Event name must not be empty.", nameof(eventName)); + } + + if (command is null) + { + return Disposable.Empty; + } + + // Parameter value may be updated on a different thread than the event callback; + // ensure a consistent publication/read. + object? latestParameter = null; + + var ret = new CompositeDisposable(); - return (IDisposable?)mi.Invoke(this, [command, target, commandParameter, eventInfo.EventInfo?.Name]); + ret.Add(commandParameter.Subscribe(static x => + { + // Store under volatile semantics. + Volatile.Write(ref Unsafe.As(ref x), x); // no-op; keeps delegate static-friendly + })); + + // The above static trick is not useful because we still need to update latestParameter; keep a single closure, + // but use Volatile for correctness. + ret.Clear(); + + ret.Add(commandParameter.Subscribe(x => Volatile.Write(ref latestParameter, x))); + + var evt = Observable.FromEventPattern(target, eventName); + + ret.Add(evt.Subscribe(_ => + { + var param = Volatile.Read(ref latestParameter); + if (command.CanExecute(param)) + { + command.Execute(param); + } + })); + + return ret; } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Event binding requires dynamic code generation")] - [RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] -#endif - public IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter, string eventName) -#if MONO + /// + /// Binds a command to a specific event on a target object using explicit add/remove handler delegates. + /// + /// The type of the target object. + /// The event arguments type. + /// The command to bind. If , no binding is created. + /// The target object. + /// An observable that supplies command parameter values. + /// Adds the handler to the target event. + /// Removes the handler from the target event. + /// A disposable that unbinds the command. + /// + /// Thrown when , , or is . + /// + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>( + ICommand? command, + T? target, + IObservable commandParameter, + Action> addHandler, + Action> removeHandler) + where T : class where TEventArgs : EventArgs -#endif { - var ret = new CompositeDisposable(); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(addHandler); + ArgumentExceptionHelper.ThrowIfNull(removeHandler); + // Preserve typical binding semantics: null command => no-op binding. + if (command is null) + { + return Disposable.Empty; + } + + // latestParameter may be produced on a different thread than the UI event. object? latestParameter = null; - var evt = Observable.FromEventPattern(target!, eventName); - ret.Add(commandParameter.Subscribe(x => latestParameter = x)); + // Stable delegate for deterministic unsubscription. + void Handler(object? s, TEventArgs e) + { + var param = Volatile.Read(ref latestParameter); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } - ret.Add(evt.Subscribe(_ => + // Subscribe to parameter changes first so the first event sees the latest parameter. + var parameterSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParameter, x)); + + // Hook the event after parameter subscription; unhook deterministically on dispose. + addHandler(Handler); + + return new CompositeDisposable( + parameterSub, + Disposable.Create(() => removeHandler(Handler))); + } + + /// + /// Binds a command to an event using explicit add/remove handler actions (non-reflection). + /// + /// The target type. + /// The command to bind. If , no binding is created. + /// The target object. + /// An observable that supplies command parameter values. + /// Adds the handler to the target event. + /// Removes the handler from the target event. + /// A disposable that unbinds the command. + /// + /// Thrown when , , or is . + /// + public IDisposable BindCommandToObject( + ICommand? command, + T? target, + IObservable commandParameter, + Action addHandler, + Action removeHandler) + where T : class + { + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(addHandler); + ArgumentExceptionHelper.ThrowIfNull(removeHandler); + + if (command is null) + { + return Disposable.Empty; + } + + object? latestParameter = null; + + // Stable delegate for deterministic unsubscription. + void Handler(object? s, EventArgs e) { - if (command!.CanExecute(latestParameter)) + var param = Volatile.Read(ref latestParameter); + if (command.CanExecute(param)) { - command.Execute(latestParameter); + command.Execute(param); } - })); + } + var ret = new CompositeDisposable + { + commandParameter.Subscribe(x => Volatile.Write(ref latestParameter, x)), + Disposable.Create(() => removeHandler(Handler)) + }; + + addHandler(Handler); return ret; } + + /// + /// Per-closed-generic cache for default event resolution. + /// + /// The target type. + private static class DefaultEventCache< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents)]T> + { + /// + /// Gets the selected default event name for , or if none exists. + /// + public static readonly string? DefaultEventName = FindDefaultEventName(); + + /// + /// Gets a value indicating whether has any default event supported by this binder. + /// + public static readonly bool HasDefaultEvent = DefaultEventName is not null; + + private static string? FindDefaultEventName() + { + var type = typeof(T); + + // Avoid LINQ allocations; scan in priority order. + for (var i = 0; i < DefaultEventsToBind.Length; i++) + { + var name = DefaultEventsToBind[i].Name; + if (type.GetRuntimeEvent(name) is not null) + { + return name; + } + } + + return null; + } + } } diff --git a/src/ReactiveUI/Bindings/Command/ICommandBinderImplementation.cs b/src/ReactiveUI/Bindings/Command/ICommandBinderImplementation.cs index 232735e000..813da4a4a7 100644 --- a/src/ReactiveUI/Bindings/Command/ICommandBinderImplementation.cs +++ b/src/ReactiveUI/Bindings/Command/ICommandBinderImplementation.cs @@ -27,20 +27,18 @@ internal interface ICommandBinderImplementation : IEnableLogger /// The type of control on the view. /// The type of the parameter to pass to the ICommand. /// A reactive binding. Often only used for disposing the binding. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - IReactiveBinding BindCommand( + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + IReactiveBinding BindCommand( TViewModel? viewModel, TView view, Expression> vmProperty, Expression> controlProperty, Expression> withParameter, string? toEvent = null) - where TView : class, IViewFor - where TViewModel : class - where TProp : ICommand; + where TView : class, IViewFor + where TViewModel : class + where TProp : ICommand + where TControl : class; /// /// Binds the command on a ViewModel to a control on the View. @@ -57,18 +55,16 @@ IReactiveBinding BindCommandThe type of control on the view. /// The type of the parameter to pass to the ICommand. /// A reactive binding. Often only used for disposing the binding. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - IReactiveBinding BindCommand( + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + IReactiveBinding BindCommand( TViewModel? viewModel, TView view, Expression> vmProperty, Expression> controlProperty, IObservable withParameter, string? toEvent = null) - where TView : class, IViewFor - where TViewModel : class - where TProp : ICommand; + where TView : class, IViewFor + where TViewModel : class + where TProp : ICommand + where TControl : class; } diff --git a/src/ReactiveUI/Bindings/Converter/BooleanToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/BooleanToStringTypeConverter.cs new file mode 100644 index 0000000000..c662b22a33 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/BooleanToStringTypeConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class BooleanToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(bool from, object? conversionHint, [NotNullWhen(true)] out string? result) + { + result = from.ToString(); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/ByteToNullableByteTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/ByteToNullableByteTypeConverter.cs new file mode 100644 index 0000000000..531a6b5f86 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/ByteToNullableByteTypeConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class ByteToNullableByteTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(byte); + + /// + public Type ToType => typeof(byte?); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(byte from, object? conversionHint, out byte? result) + { + result = from; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is byte value) + { + result = (byte?)value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/ByteToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/ByteToStringTypeConverter.cs index e6b4495f3e..75ed90f32b 100644 --- a/src/ReactiveUI/Bindings/Converter/ByteToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/ByteToStringTypeConverter.cs @@ -1,59 +1,36 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Short To String Type Converter. +/// Converts values to . /// -/// -public class ByteToStringTypeConverter : IBindingTypeConverter +public sealed class ByteToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(byte) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(byte)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(byte from, object? conversionHint, [NotNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is byte fromByte) + if (conversionHint is int width) { - if (conversionHint is int byteHint) - { - result = fromByte.ToString($"D{byteHint}"); - return true; - } - - result = fromByte.ToString(); + result = from.ToString($"D{width}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - var success = byte.TryParse(fromString, out var outByte); - if (success) - { - result = outByte; - - return true; - } + result = from.ToString(format); + return true; } - result = null!; - return false; + result = from.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/DateOnlyToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/DateOnlyToStringTypeConverter.cs new file mode 100644 index 0000000000..8f3e8ec69c --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/DateOnlyToStringTypeConverter.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2025 .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 NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace ReactiveUI; + +/// +/// Converts to . +/// +public sealed class DateOnlyToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(DateOnly from, object? conversionHint, [NotNullWhen(true)] out string? result) + { + result = from.ToString(); + return true; + } +} +#endif diff --git a/src/ReactiveUI/Bindings/Converter/DateTimeOffsetToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/DateTimeOffsetToStringTypeConverter.cs new file mode 100644 index 0000000000..0635a4f029 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/DateTimeOffsetToStringTypeConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class DateTimeOffsetToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(DateTimeOffset from, object? conversionHint, [NotNullWhen(true)] out string? result) + { + result = from.ToString(); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/DateTimeToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/DateTimeToStringTypeConverter.cs new file mode 100644 index 0000000000..91e9dd110f --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/DateTimeToStringTypeConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class DateTimeToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(DateTime from, object? conversionHint, [NotNullWhen(true)] out string? result) + { + result = from.ToString(); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/DecimalToNullableDecimalTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/DecimalToNullableDecimalTypeConverter.cs new file mode 100644 index 0000000000..70917963f3 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/DecimalToNullableDecimalTypeConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class DecimalToNullableDecimalTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(decimal); + + /// + public Type ToType => typeof(decimal?); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(decimal from, object? conversionHint, out decimal? result) + { + result = from; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is decimal value) + { + result = (decimal?)value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/DecimalToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/DecimalToStringTypeConverter.cs index 06d050fa01..f7d3802edf 100644 --- a/src/ReactiveUI/Bindings/Converter/DecimalToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/DecimalToStringTypeConverter.cs @@ -1,65 +1,36 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Decimal To String Type Converter. +/// Converts values to . /// -/// -public class DecimalToStringTypeConverter : IBindingTypeConverter +public sealed class DecimalToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(decimal) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(decimal)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(decimal from, object? conversionHint, [NotNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is decimal fromDecimal) + if (conversionHint is int decimalPlaces) { - if (conversionHint is int decimalHint) - { - result = fromDecimal.ToString($"F{decimalHint}"); - return true; - } - - result = fromDecimal.ToString(); + result = from.ToString($"F{decimalPlaces}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - var success = decimal.TryParse(fromString, out var outDecimal); - if (success) - { - if (conversionHint is int decimalHint) - { - result = Math.Round(outDecimal, decimalHint); - return true; - } - - result = outDecimal; - - return true; - } + result = from.ToString(format); + return true; } - result = null!; - return false; + result = from.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/DoubleToNullableDoubleTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/DoubleToNullableDoubleTypeConverter.cs new file mode 100644 index 0000000000..bd9230df87 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/DoubleToNullableDoubleTypeConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class DoubleToNullableDoubleTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(double); + + /// + public Type ToType => typeof(double?); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(double from, object? conversionHint, out double? result) + { + result = from; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is double value) + { + result = (double?)value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/DoubleToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/DoubleToStringTypeConverter.cs index 47e6fe8ba3..21d2c9c0ee 100644 --- a/src/ReactiveUI/Bindings/Converter/DoubleToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/DoubleToStringTypeConverter.cs @@ -1,65 +1,36 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Double To String Type Converter. +/// Converts values to . /// -/// -public class DoubleToStringTypeConverter : IBindingTypeConverter +public sealed class DoubleToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(double) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(double)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(double from, object? conversionHint, [NotNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is double fromDouble) + if (conversionHint is int decimalPlaces) { - if (conversionHint is int doubleHint) - { - result = fromDouble.ToString($"F{doubleHint}"); - return true; - } - - result = fromDouble.ToString(); + result = from.ToString($"F{decimalPlaces}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - var success = double.TryParse(fromString, out var outDouble); - if (success) - { - if (conversionHint is int doubleHint) - { - result = Math.Round(outDouble, doubleHint); - return true; - } - - result = outDouble; - - return true; - } + result = from.ToString(format); + return true; } - result = null!; - return false; + result = from.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/EqualityTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/EqualityTypeConverter.cs index 6278fc8e07..f617773a92 100644 --- a/src/ReactiveUI/Bindings/Converter/EqualityTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/EqualityTypeConverter.cs @@ -1,124 +1,41 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; +using System.Diagnostics.CodeAnalysis; namespace ReactiveUI; /// -/// The default converter, simply converts between types that are equal or -/// can be converted (i.e. Button => UIControl). +/// Converts any value to by comparing it with a hint value using . /// -public class EqualityTypeConverter : IBindingTypeConverter +/// +/// +/// This converter is useful for binding scenarios where you need to determine if a value +/// equals a specific comparison value. The comparison value should be provided via the +/// conversionHint parameter. +/// +/// +/// Example: Convert an enum value to bool by comparing with a specific enum member. +/// +/// +public sealed class EqualityTypeConverter : IBindingTypeConverter { - private static readonly MemoizingMRUCache _referenceCastCache = new( - static (_, _) => _methodInfo ??= typeof(EqualityTypeConverter).GetRuntimeMethods().First(static x => x.Name == nameof(DoReferenceCast)), RxApp.SmallCacheLimit); - - private static MethodInfo? _methodInfo; - - /// - /// Handles casting for a reference. Understands about nullable types - /// and can cast appropriately. - /// - /// The object we are casting from. - /// The target we want to cast to. - /// The new value after it has been casted. - /// If we cannot cast the object. - public static object? DoReferenceCast(object? from, Type targetType) - { - ArgumentExceptionHelper.ThrowIfNull(targetType); - var backingNullableType = Nullable.GetUnderlyingType(targetType); - - if (backingNullableType is null) - { - if (from is null) - { - if (targetType.GetTypeInfo().IsValueType) - { - throw new InvalidCastException("Can't convert from nullable-type which is null to non-nullable type"); - } - - return null; - } - - if (IsInstanceOfType(from, targetType)) - { - return from; - } - - throw new InvalidCastException(); - } - - if (from is null) - { - return null; - } - - var converted = Convert.ChangeType(from, backingNullableType, null); - if (!IsInstanceOfType(converted, targetType)) - { - throw new InvalidCastException(); - } - - return converted; - } - /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (toType.GetTypeInfo().IsAssignableFrom(fromType.GetTypeInfo())) - { - return 100; - } - - // NB: WPF is terrible. - if (fromType == typeof(object)) - { - return 100; - } - - var realType = Nullable.GetUnderlyingType(fromType); - if (realType is not null) - { - return GetAffinityForObjects(realType, toType); - } + public Type FromType => typeof(object); - realType = Nullable.GetUnderlyingType(toType); - if (realType is not null) - { - return GetAffinityForObjects(fromType, realType); - } + /// + public Type ToType => typeof(bool); - return 0; - } + /// + public int GetAffinityForObjects() => 1; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result) + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) { - ArgumentExceptionHelper.ThrowIfNull(toType); - - var mi = _referenceCastCache.Get(toType); - - try - { - result = mi.Invoke(null, [from, toType]); - } - catch (Exception ex) - { - this.Log().Warn(ex, "Couldn't convert object to type: " + toType); - result = null; - return false; - } - + // Always return a bool result + result = Equals(from, conversionHint); return true; } - - private static bool IsInstanceOfType(object from, Type targetType) - { - ArgumentExceptionHelper.ThrowIfNull(from); - ArgumentExceptionHelper.ThrowIfNull(targetType); - return targetType.IsInstanceOfType(from); - } } diff --git a/src/ReactiveUI/Bindings/Converter/GuidToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/GuidToStringTypeConverter.cs new file mode 100644 index 0000000000..5fd87c6e1c --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/GuidToStringTypeConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using the "D" format (standard hyphenated format). +/// +public sealed class GuidToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(Guid from, object? conversionHint, [NotNullWhen(true)] out string? result) + { + result = from.ToString("D"); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/IntegerToNullableIntegerTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/IntegerToNullableIntegerTypeConverter.cs new file mode 100644 index 0000000000..6995f2c962 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/IntegerToNullableIntegerTypeConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class IntegerToNullableIntegerTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(int); + + /// + public Type ToType => typeof(int?); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(int from, object? conversionHint, out int? result) + { + result = from; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is int value) + { + result = (int?)value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/IntegerToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/IntegerToStringTypeConverter.cs index b2267e6b74..5fb54d7d2b 100644 --- a/src/ReactiveUI/Bindings/Converter/IntegerToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/IntegerToStringTypeConverter.cs @@ -1,59 +1,36 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Integer To String Type Converter. +/// Converts values to . /// -/// -public class IntegerToStringTypeConverter : IBindingTypeConverter +public sealed class IntegerToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(int) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(int)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(int from, object? conversionHint, [NotNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is int fromInt) + if (conversionHint is int width) { - if (conversionHint is int intHint) - { - result = fromInt.ToString($"D{intHint}"); - return true; - } - - result = fromInt.ToString(); + result = from.ToString($"D{width}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - var success = int.TryParse(fromString, out var outInt); - if (success) - { - result = outInt; - - return true; - } + result = from.ToString(format); + return true; } - result = null!; - return false; + result = from.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/LongToNullableLongTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/LongToNullableLongTypeConverter.cs new file mode 100644 index 0000000000..76b6379e63 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/LongToNullableLongTypeConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class LongToNullableLongTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(long); + + /// + public Type ToType => typeof(long?); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(long from, object? conversionHint, out long? result) + { + result = from; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is long value) + { + result = (long?)value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/LongToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/LongToStringTypeConverter.cs index 2509908a76..f8b1c0d785 100644 --- a/src/ReactiveUI/Bindings/Converter/LongToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/LongToStringTypeConverter.cs @@ -1,59 +1,36 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Integer To String Type Converter. +/// Converts values to . /// -/// -public class LongToStringTypeConverter : IBindingTypeConverter +public sealed class LongToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(long) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(long)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(long from, object? conversionHint, [NotNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is long fromLong) + if (conversionHint is int width) { - if (conversionHint is int longHint) - { - result = fromLong.ToString($"D{longHint}"); - return true; - } - - result = fromLong.ToString(); + result = from.ToString($"D{width}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - var success = long.TryParse(fromString, out var outLong); - if (success) - { - result = outLong; - - return true; - } + result = from.ToString(format); + return true; } - result = null!; - return false; + result = from.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/NullableBooleanToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableBooleanToStringTypeConverter.cs new file mode 100644 index 0000000000..a237e84c44 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableBooleanToStringTypeConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts nullable to . +/// +public sealed class NullableBooleanToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(bool? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) + { + if (!from.HasValue) + { + result = null; + return true; + } + + result = from.Value.ToString(); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableByteToByteTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableByteToByteTypeConverter.cs new file mode 100644 index 0000000000..4b43fbbe60 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableByteToByteTypeConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +/// +/// When the nullable value is null, returns the default value (0). +/// +public sealed class NullableByteToByteTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(byte?); + + /// + public Type ToType => typeof(byte); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(byte? from, object? conversionHint, [NotNullWhen(true)] out byte result) + { + result = from ?? 0; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is null) + { + result = (byte)0; + return true; + } + + if (from is byte value) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableByteToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableByteToStringTypeConverter.cs index 6354066309..b7244a194d 100644 --- a/src/ReactiveUI/Bindings/Converter/NullableByteToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/NullableByteToStringTypeConverter.cs @@ -1,71 +1,42 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Short To String Type Converter. +/// Converts nullable values to . /// -/// -public class NullableByteToStringTypeConverter : IBindingTypeConverter +public sealed class NullableByteToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(byte?) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(byte?)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(byte? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is byte fromByte) + if (!from.HasValue) { - if (conversionHint is int byteHint) - { - result = fromByte.ToString($"D{byteHint}"); - return true; - } - - result = fromByte.ToString(); + result = null; return true; } - if (from is null) + if (conversionHint is int width) { - result = null!; + result = from.Value.ToString($"D{width}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - if (string.IsNullOrEmpty(fromString)) - { - result = null!; - return true; - } - - var success = byte.TryParse(fromString, out var outByte); - if (success) - { - result = outByte; - - return true; - } + result = from.Value.ToString(format); + return true; } - result = null!; - return false; + result = from.Value.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/NullableDateOnlyToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableDateOnlyToStringTypeConverter.cs new file mode 100644 index 0000000000..250d340837 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableDateOnlyToStringTypeConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2025 .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 NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace ReactiveUI; + +/// +/// Converts nullable to . +/// +public sealed class NullableDateOnlyToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(DateOnly? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) + { + if (!from.HasValue) + { + result = null; + return true; + } + + result = from.Value.ToString(); + return true; + } +} +#endif diff --git a/src/ReactiveUI/Bindings/Converter/NullableDateTimeOffsetToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableDateTimeOffsetToStringTypeConverter.cs new file mode 100644 index 0000000000..93706579b8 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableDateTimeOffsetToStringTypeConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts nullable to . +/// +public sealed class NullableDateTimeOffsetToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(DateTimeOffset? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) + { + if (!from.HasValue) + { + result = null; + return true; + } + + result = from.Value.ToString(); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableDateTimeToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableDateTimeToStringTypeConverter.cs new file mode 100644 index 0000000000..38f23ab25d --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableDateTimeToStringTypeConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts nullable to . +/// +public sealed class NullableDateTimeToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(DateTime? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) + { + if (!from.HasValue) + { + result = null; + return true; + } + + result = from.Value.ToString(); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableDecimalToDecimalTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableDecimalToDecimalTypeConverter.cs new file mode 100644 index 0000000000..c53c19f4ce --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableDecimalToDecimalTypeConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +/// +/// When the nullable value is null, returns the default value (0.0M). +/// +public sealed class NullableDecimalToDecimalTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(decimal?); + + /// + public Type ToType => typeof(decimal); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(decimal? from, object? conversionHint, [NotNullWhen(true)] out decimal result) + { + result = from ?? 0.0M; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is null) + { + result = 0.0M; + return true; + } + + if (from is decimal value) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableDecimalToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableDecimalToStringTypeConverter.cs index 8e948034e1..d4689c21d3 100644 --- a/src/ReactiveUI/Bindings/Converter/NullableDecimalToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/NullableDecimalToStringTypeConverter.cs @@ -1,77 +1,42 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Decimal To String Type Converter. +/// Converts nullable values to . /// -/// -public class NullableDecimalToStringTypeConverter : IBindingTypeConverter +public sealed class NullableDecimalToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(decimal?) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(decimal?)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(decimal? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is decimal fromDecimal) + if (!from.HasValue) { - if (conversionHint is int decimalHint) - { - result = fromDecimal.ToString($"F{decimalHint}"); - return true; - } - - result = fromDecimal.ToString(); + result = null; return true; } - if (from is null) + if (conversionHint is int decimalPlaces) { - result = null!; + result = from.Value.ToString($"F{decimalPlaces}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - if (string.IsNullOrEmpty(fromString)) - { - result = null!; - return true; - } - - var success = decimal.TryParse(fromString, out var outDecimal); - if (success) - { - if (conversionHint is int decimalHint) - { - result = Math.Round(outDecimal, decimalHint); - return true; - } - - result = outDecimal; - - return true; - } + result = from.Value.ToString(format); + return true; } - result = null!; - return false; + result = from.Value.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/NullableDoubleToDoubleTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableDoubleToDoubleTypeConverter.cs new file mode 100644 index 0000000000..ecfbfb42c5 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableDoubleToDoubleTypeConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +/// +/// When the nullable value is null, returns the default value (0.0). +/// +public sealed class NullableDoubleToDoubleTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(double?); + + /// + public Type ToType => typeof(double); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(double? from, object? conversionHint, [NotNullWhen(true)] out double result) + { + result = from ?? 0.0; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is null) + { + result = 0.0; + return true; + } + + if (from is double value) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableDoubleToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableDoubleToStringTypeConverter.cs index 6d2fac6e50..8332254145 100644 --- a/src/ReactiveUI/Bindings/Converter/NullableDoubleToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/NullableDoubleToStringTypeConverter.cs @@ -1,77 +1,42 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Double To String Type Converter. +/// Converts nullable values to . /// -/// -public class NullableDoubleToStringTypeConverter : IBindingTypeConverter +public sealed class NullableDoubleToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(double?) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(double?)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(double? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is double fromDouble) + if (!from.HasValue) { - if (conversionHint is int doubleHint) - { - result = fromDouble.ToString($"F{doubleHint}"); - return true; - } - - result = fromDouble.ToString(); + result = null; return true; } - if (from is null) + if (conversionHint is int decimalPlaces) { - result = null!; + result = from.Value.ToString($"F{decimalPlaces}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - if (string.IsNullOrEmpty(fromString)) - { - result = null!; - return true; - } - - var success = double.TryParse(fromString, out var outDouble); - if (success) - { - if (conversionHint is int doubleHint) - { - result = Math.Round(outDouble, doubleHint); - return true; - } - - result = outDouble; - - return true; - } + result = from.Value.ToString(format); + return true; } - result = null!; - return false; + result = from.Value.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/NullableGuidToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableGuidToStringTypeConverter.cs new file mode 100644 index 0000000000..9cf69c2ee2 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableGuidToStringTypeConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts nullable to using the "D" format (standard hyphenated format). +/// +public sealed class NullableGuidToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(Guid? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) + { + if (!from.HasValue) + { + result = null; + return true; + } + + result = from.Value.ToString("D"); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableIntegerToIntegerTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableIntegerToIntegerTypeConverter.cs new file mode 100644 index 0000000000..ec330f52b1 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableIntegerToIntegerTypeConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +/// +/// When the nullable value is null, returns the default value (0). +/// +public sealed class NullableIntegerToIntegerTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(int?); + + /// + public Type ToType => typeof(int); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(int? from, object? conversionHint, [NotNullWhen(true)] out int result) + { + result = from ?? 0; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is null) + { + result = 0; + return true; + } + + if (from is int value) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableIntegerToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableIntegerToStringTypeConverter.cs index 8e74ae5482..2ce2a99658 100644 --- a/src/ReactiveUI/Bindings/Converter/NullableIntegerToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/NullableIntegerToStringTypeConverter.cs @@ -1,71 +1,42 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Integer To String Type Converter. +/// Converts nullable values to . /// -/// -public class NullableIntegerToStringTypeConverter : IBindingTypeConverter +public sealed class NullableIntegerToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(int?) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(int?)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(int? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is int fromInt) + if (!from.HasValue) { - if (conversionHint is int intHint) - { - result = fromInt.ToString($"D{intHint}"); - return true; - } - - result = fromInt.ToString(); + result = null; return true; } - if (from is null) + if (conversionHint is int width) { - result = null!; + result = from.Value.ToString($"D{width}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - if (string.IsNullOrEmpty(fromString)) - { - result = null!; - return true; - } - - var success = int.TryParse(fromString, out var outInt); - if (success) - { - result = outInt; - - return true; - } + result = from.Value.ToString(format); + return true; } - result = null!; - return false; + result = from.Value.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/NullableLongToLongTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableLongToLongTypeConverter.cs new file mode 100644 index 0000000000..8594c74075 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableLongToLongTypeConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +/// +/// When the nullable value is null, returns the default value (0). +/// +public sealed class NullableLongToLongTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(long?); + + /// + public Type ToType => typeof(long); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(long? from, object? conversionHint, [NotNullWhen(true)] out long result) + { + result = from ?? 0; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is null) + { + result = 0L; + return true; + } + + if (from is long value) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableLongToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableLongToStringTypeConverter.cs index 167dc266ce..986ffbc350 100644 --- a/src/ReactiveUI/Bindings/Converter/NullableLongToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/NullableLongToStringTypeConverter.cs @@ -1,71 +1,42 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Integer To String Type Converter. +/// Converts nullable values to . /// -/// -public class NullableLongToStringTypeConverter : IBindingTypeConverter +public sealed class NullableLongToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(long?) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(long?)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(long? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is long fromLong) + if (!from.HasValue) { - if (conversionHint is int longHint) - { - result = fromLong.ToString($"D{longHint}"); - return true; - } - - result = fromLong.ToString(); + result = null; return true; } - if (from is null) + if (conversionHint is int width) { - result = null!; + result = from.Value.ToString($"D{width}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - if (string.IsNullOrEmpty(fromString)) - { - result = null!; - return true; - } - - var success = long.TryParse(fromString, out var outLong); - if (success) - { - result = outLong; - - return true; - } + result = from.Value.ToString(format); + return true; } - result = null!; - return false; + result = from.Value.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/NullableShortToShortTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableShortToShortTypeConverter.cs new file mode 100644 index 0000000000..f87fc1dfcf --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableShortToShortTypeConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +/// +/// When the nullable value is null, returns the default value (0). +/// +public sealed class NullableShortToShortTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(short?); + + /// + public Type ToType => typeof(short); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(short? from, object? conversionHint, [NotNullWhen(true)] out short result) + { + result = from ?? 0; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is null) + { + result = (short)0; + return true; + } + + if (from is short value) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableShortToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableShortToStringTypeConverter.cs index b13cade79f..01947d3b7b 100644 --- a/src/ReactiveUI/Bindings/Converter/NullableShortToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/NullableShortToStringTypeConverter.cs @@ -1,71 +1,42 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Short To String Type Converter. +/// Converts nullable values to . /// -/// -public class NullableShortToStringTypeConverter : IBindingTypeConverter +public sealed class NullableShortToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(short?) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(short?)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(short? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is short fromShort) + if (!from.HasValue) { - if (conversionHint is int shortHint) - { - result = fromShort.ToString($"D{shortHint}"); - return true; - } - - result = fromShort.ToString(); + result = null; return true; } - if (from is null) + if (conversionHint is int width) { - result = null!; + result = from.Value.ToString($"D{width}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - if (string.IsNullOrEmpty(fromString)) - { - result = null!; - return true; - } - - var success = short.TryParse(fromString, out var outShort); - if (success) - { - result = outShort; - - return true; - } + result = from.Value.ToString(format); + return true; } - result = null!; - return false; + result = from.Value.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/NullableSingleToSingleTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableSingleToSingleTypeConverter.cs new file mode 100644 index 0000000000..0fa9a212d9 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableSingleToSingleTypeConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +/// +/// When the nullable value is null, returns the default value (0.0f). +/// +public sealed class NullableSingleToSingleTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(float?); + + /// + public Type ToType => typeof(float); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(float? from, object? conversionHint, [NotNullWhen(true)] out float result) + { + result = from ?? 0.0f; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is null) + { + result = 0.0f; + return true; + } + + if (from is float value) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/NullableSingleToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableSingleToStringTypeConverter.cs index 5e18b145d0..240abd2f38 100644 --- a/src/ReactiveUI/Bindings/Converter/NullableSingleToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/NullableSingleToStringTypeConverter.cs @@ -1,77 +1,42 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Single To String Type Converter. +/// Converts nullable values to . /// -/// -public class NullableSingleToStringTypeConverter : IBindingTypeConverter +public sealed class NullableSingleToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(float?) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(float?)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(float? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is float fromSingle) + if (!from.HasValue) { - if (conversionHint is int singleHint) - { - result = fromSingle.ToString($"F{singleHint}"); - return true; - } - - result = fromSingle.ToString(); + result = null; return true; } - if (from is null) + if (conversionHint is int decimalPlaces) { - result = null!; + result = from.Value.ToString($"F{decimalPlaces}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - if (string.IsNullOrEmpty(fromString)) - { - result = null!; - return true; - } - - var success = float.TryParse(fromString, out var outSingle); - if (success) - { - if (conversionHint is int singleHint) - { - result = Convert.ToSingle(Math.Round(outSingle, singleHint)); - return true; - } - - result = outSingle; - - return true; - } + result = from.Value.ToString(format); + return true; } - result = null!; - return false; + result = from.Value.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/NullableTimeOnlyToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableTimeOnlyToStringTypeConverter.cs new file mode 100644 index 0000000000..3758ea88cd --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableTimeOnlyToStringTypeConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2025 .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 NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace ReactiveUI; + +/// +/// Converts nullable to . +/// +public sealed class NullableTimeOnlyToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(TimeOnly? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) + { + if (!from.HasValue) + { + result = null; + return true; + } + + result = from.Value.ToString(); + return true; + } +} +#endif diff --git a/src/ReactiveUI/Bindings/Converter/NullableTimeSpanToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/NullableTimeSpanToStringTypeConverter.cs new file mode 100644 index 0000000000..2b60a1e337 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/NullableTimeSpanToStringTypeConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts nullable to . +/// +public sealed class NullableTimeSpanToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(TimeSpan? from, object? conversionHint, [MaybeNullWhen(true)] out string? result) + { + if (!from.HasValue) + { + result = null; + return true; + } + + result = from.Value.ToString(); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/ShortToNullableShortTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/ShortToNullableShortTypeConverter.cs new file mode 100644 index 0000000000..0a68f628ca --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/ShortToNullableShortTypeConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class ShortToNullableShortTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(short); + + /// + public Type ToType => typeof(short?); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(short from, object? conversionHint, out short? result) + { + result = from; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is short value) + { + result = (short?)value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/ShortToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/ShortToStringTypeConverter.cs index a6b20bc6d7..8c108acad7 100644 --- a/src/ReactiveUI/Bindings/Converter/ShortToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/ShortToStringTypeConverter.cs @@ -1,59 +1,36 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Short To String Type Converter. +/// Converts values to . /// -/// -public class ShortToStringTypeConverter : IBindingTypeConverter +public sealed class ShortToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(short) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(short)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(short from, object? conversionHint, [NotNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is short fromShort) + if (conversionHint is int width) { - if (conversionHint is int shortHint) - { - result = fromShort.ToString($"D{shortHint}"); - return true; - } - - result = fromShort.ToString(); + result = from.ToString($"D{width}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - var success = short.TryParse(fromString, out var outShort); - if (success) - { - result = outShort; - - return true; - } + result = from.ToString(format); + return true; } - result = null!; - return false; + result = from.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/SingleToNullableSingleTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/SingleToNullableSingleTypeConverter.cs new file mode 100644 index 0000000000..25b222986b --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/SingleToNullableSingleTypeConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class SingleToNullableSingleTypeConverter : IBindingTypeConverter +{ + /// + public Type FromType => typeof(float); + + /// + public Type ToType => typeof(float?); + + /// + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvert(float from, object? conversionHint, out float? result) + { + result = from; + return true; + } + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) + { + if (from is float value) + { + result = (float?)value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/SingleToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/SingleToStringTypeConverter.cs index 29ed40e089..d6e37ec120 100644 --- a/src/ReactiveUI/Bindings/Converter/SingleToStringTypeConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/SingleToStringTypeConverter.cs @@ -1,65 +1,36 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Single To String Type Converter. +/// Converts values to . /// -/// -public class SingleToStringTypeConverter : IBindingTypeConverter +public sealed class SingleToStringTypeConverter : BindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(float) && toType == typeof(string)) - { - return 10; - } - - if (fromType == typeof(string) && toType == typeof(float)) - { - return 10; - } - - return 0; - } + public override int GetAffinityForObjects() => 2; /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + public override bool TryConvert(float from, object? conversionHint, [NotNullWhen(true)] out string? result) { - if (toType == typeof(string) && from is float fromSingle) + if (conversionHint is int decimalPlaces) { - if (conversionHint is int singleHint) - { - result = fromSingle.ToString($"F{singleHint}"); - return true; - } - - result = fromSingle.ToString(); + result = from.ToString($"F{decimalPlaces}"); return true; } - if (from is string fromString) + if (conversionHint is string format) { - var success = float.TryParse(fromString, out var outSingle); - if (success) - { - if (conversionHint is int singleHint) - { - result = Convert.ToSingle(Math.Round(outSingle, singleHint)); - return true; - } - - result = outSingle; - - return true; - } + result = from.ToString(format); + return true; } - result = null!; - return false; + result = from.ToString(); + return true; } } diff --git a/src/ReactiveUI/Bindings/Converter/StringConverter.cs b/src/ReactiveUI/Bindings/Converter/StringConverter.cs index 6d253d31fd..8894829063 100644 --- a/src/ReactiveUI/Bindings/Converter/StringConverter.cs +++ b/src/ReactiveUI/Bindings/Converter/StringConverter.cs @@ -1,24 +1,46 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; /// -/// Calls ToString on types. In WPF, ComponentTypeConverter should win -/// instead of this, since It's Better™. +/// Converts to (identity converter). /// -public class StringConverter : IBindingTypeConverter +/// +/// This converter provides a fast path for string-to-string bindings without +/// requiring reflection or TypeDescriptor. +/// +public sealed class StringConverter : IBindingTypeConverter { /// - public int GetAffinityForObjects(Type fromType, Type toType) => toType == typeof(string) ? 2 : 0; + public Type FromType => typeof(string); + + /// + public Type ToType => typeof(string); /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result) + public int GetAffinityForObjects() => 2; + + /// + public bool TryConvertTyped(object? from, object? conversionHint, [NotNullWhen(true)] out object? result) { - // XXX: All Of The Localization - result = from?.ToString(); - return true; + if (from is null) + { + result = null; + return false; + } + + if (from is string s) + { + result = s; + return true; + } + + result = null; + return false; } } diff --git a/src/ReactiveUI/Bindings/Converter/StringToBooleanTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToBooleanTypeConverter.cs new file mode 100644 index 0000000000..a50977ee54 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToBooleanTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToBooleanTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out bool result) + { + if (from is null) + { + result = default; + return false; + } + + return bool.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToByteTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToByteTypeConverter.cs new file mode 100644 index 0000000000..2976ab6988 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToByteTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToByteTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out byte result) + { + if (from is null) + { + result = default; + return false; + } + + return byte.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToDateOnlyTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToDateOnlyTypeConverter.cs new file mode 100644 index 0000000000..3bbe7ff51c --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToDateOnlyTypeConverter.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2025 .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 NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace ReactiveUI; + +/// +/// Converts to using . +/// +public sealed class StringToDateOnlyTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out DateOnly result) + { + if (from is null) + { + result = default; + return false; + } + + return DateOnly.TryParse(from, out result); + } +} +#endif diff --git a/src/ReactiveUI/Bindings/Converter/StringToDateTimeOffsetTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToDateTimeOffsetTypeConverter.cs new file mode 100644 index 0000000000..2c1313892a --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToDateTimeOffsetTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToDateTimeOffsetTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out DateTimeOffset result) + { + if (from is null) + { + result = default; + return false; + } + + return DateTimeOffset.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToDateTimeTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToDateTimeTypeConverter.cs new file mode 100644 index 0000000000..f056daac75 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToDateTimeTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToDateTimeTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out DateTime result) + { + if (from is null) + { + result = default; + return false; + } + + return DateTime.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToDecimalTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToDecimalTypeConverter.cs new file mode 100644 index 0000000000..e5163926a9 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToDecimalTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToDecimalTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out decimal result) + { + if (from is null) + { + result = default; + return false; + } + + return decimal.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToDoubleTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToDoubleTypeConverter.cs new file mode 100644 index 0000000000..40e7fc8dbb --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToDoubleTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToDoubleTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out double result) + { + if (from is null) + { + result = default; + return false; + } + + return double.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToGuidTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToGuidTypeConverter.cs new file mode 100644 index 0000000000..121869a45a --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToGuidTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToGuidTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out Guid result) + { + if (from is null) + { + result = default; + return false; + } + + return Guid.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToIntegerTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToIntegerTypeConverter.cs new file mode 100644 index 0000000000..5b631ca5a3 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToIntegerTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToIntegerTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out int result) + { + if (from is null) + { + result = default; + return false; + } + + return int.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToLongTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToLongTypeConverter.cs new file mode 100644 index 0000000000..19829fcc62 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToLongTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToLongTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out long result) + { + if (from is null) + { + result = default; + return false; + } + + return long.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableBooleanTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableBooleanTypeConverter.cs new file mode 100644 index 0000000000..8dca133d12 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableBooleanTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableBooleanTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out bool? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (bool.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableByteTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableByteTypeConverter.cs new file mode 100644 index 0000000000..44c4f94b19 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableByteTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableByteTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out byte? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (byte.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableDateOnlyTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableDateOnlyTypeConverter.cs new file mode 100644 index 0000000000..58862c62eb --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableDateOnlyTypeConverter.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2025 .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 NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace ReactiveUI; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableDateOnlyTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out DateOnly? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (DateOnly.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} +#endif diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableDateTimeOffsetTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableDateTimeOffsetTypeConverter.cs new file mode 100644 index 0000000000..61a96badc6 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableDateTimeOffsetTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableDateTimeOffsetTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out DateTimeOffset? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (DateTimeOffset.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableDateTimeTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableDateTimeTypeConverter.cs new file mode 100644 index 0000000000..33ddda92bb --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableDateTimeTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableDateTimeTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out DateTime? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (DateTime.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableDecimalTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableDecimalTypeConverter.cs new file mode 100644 index 0000000000..1b1c10e128 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableDecimalTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableDecimalTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out decimal? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (decimal.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableDoubleTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableDoubleTypeConverter.cs new file mode 100644 index 0000000000..5c4cd9e8a7 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableDoubleTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableDoubleTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out double? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (double.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableGuidTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableGuidTypeConverter.cs new file mode 100644 index 0000000000..4f52a7e9ec --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableGuidTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableGuidTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out Guid? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (Guid.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableIntegerTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableIntegerTypeConverter.cs new file mode 100644 index 0000000000..8338a512ee --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableIntegerTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableIntegerTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out int? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (int.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableLongTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableLongTypeConverter.cs new file mode 100644 index 0000000000..c7818e8af7 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableLongTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableLongTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out long? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (long.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableShortTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableShortTypeConverter.cs new file mode 100644 index 0000000000..c9d1ad8027 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableShortTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableShortTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out short? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (short.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableSingleTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableSingleTypeConverter.cs new file mode 100644 index 0000000000..559b828b0a --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableSingleTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableSingleTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out float? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (float.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableTimeOnlyTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableTimeOnlyTypeConverter.cs new file mode 100644 index 0000000000..ac0f877196 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableTimeOnlyTypeConverter.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2025 .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 NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace ReactiveUI; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableTimeOnlyTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out TimeOnly? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (TimeOnly.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} +#endif diff --git a/src/ReactiveUI/Bindings/Converter/StringToNullableTimeSpanTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToNullableTimeSpanTypeConverter.cs new file mode 100644 index 0000000000..016b42c164 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToNullableTimeSpanTypeConverter.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to nullable using . +/// +public sealed class StringToNullableTimeSpanTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [MaybeNullWhen(true)] out TimeSpan? result) + { + if (string.IsNullOrEmpty(from)) + { + result = null; + return true; + } + + if (TimeSpan.TryParse(from, out var value)) + { + result = value; + return true; + } + + result = null; + return false; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToShortTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToShortTypeConverter.cs new file mode 100644 index 0000000000..0f8a1e7a37 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToShortTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToShortTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out short result) + { + if (from is null) + { + result = default; + return false; + } + + return short.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToSingleTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToSingleTypeConverter.cs new file mode 100644 index 0000000000..2a75d7a48b --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToSingleTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToSingleTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out float result) + { + if (from is null) + { + result = default; + return false; + } + + return float.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToTimeOnlyTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToTimeOnlyTypeConverter.cs new file mode 100644 index 0000000000..6ec937cacc --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToTimeOnlyTypeConverter.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2025 .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 NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace ReactiveUI; + +/// +/// Converts to using . +/// +public sealed class StringToTimeOnlyTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out TimeOnly result) + { + if (from is null) + { + result = default; + return false; + } + + return TimeOnly.TryParse(from, out result); + } +} +#endif diff --git a/src/ReactiveUI/Bindings/Converter/StringToTimeSpanTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToTimeSpanTypeConverter.cs new file mode 100644 index 0000000000..5b584b0f82 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToTimeSpanTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToTimeSpanTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out TimeSpan result) + { + if (from is null) + { + result = default; + return false; + } + + return TimeSpan.TryParse(from, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/StringToUriTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/StringToUriTypeConverter.cs new file mode 100644 index 0000000000..4aaf4a2a79 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/StringToUriTypeConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to using . +/// +public sealed class StringToUriTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(string? from, object? conversionHint, [NotNullWhen(true)] out Uri? result) + { + if (from is null) + { + result = null; + return false; + } + + return Uri.TryCreate(from, UriKind.RelativeOrAbsolute, out result); + } +} diff --git a/src/ReactiveUI/Bindings/Converter/TimeOnlyToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/TimeOnlyToStringTypeConverter.cs new file mode 100644 index 0000000000..878db2c26b --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/TimeOnlyToStringTypeConverter.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2025 .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 NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace ReactiveUI; + +/// +/// Converts to . +/// +public sealed class TimeOnlyToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(TimeOnly from, object? conversionHint, [NotNullWhen(true)] out string? result) + { + result = from.ToString(); + return true; + } +} +#endif diff --git a/src/ReactiveUI/Bindings/Converter/TimeSpanToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/TimeSpanToStringTypeConverter.cs new file mode 100644 index 0000000000..2732f7b055 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/TimeSpanToStringTypeConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class TimeSpanToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(TimeSpan from, object? conversionHint, [NotNullWhen(true)] out string? result) + { + result = from.ToString(); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converter/UriToStringTypeConverter.cs b/src/ReactiveUI/Bindings/Converter/UriToStringTypeConverter.cs new file mode 100644 index 0000000000..532daf6293 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converter/UriToStringTypeConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2025 .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; + +/// +/// Converts to . +/// +public sealed class UriToStringTypeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 2; + + /// + public override bool TryConvert(Uri? from, object? conversionHint, [NotNullWhen(true)] out string? result) + { + if (from is null) + { + result = null; + return false; + } + + result = from.ToString(); + return true; + } +} diff --git a/src/ReactiveUI/Bindings/Converters/BindingFallbackConverterRegistry.cs b/src/ReactiveUI/Bindings/Converters/BindingFallbackConverterRegistry.cs new file mode 100644 index 0000000000..143137657d --- /dev/null +++ b/src/ReactiveUI/Bindings/Converters/BindingFallbackConverterRegistry.cs @@ -0,0 +1,182 @@ +// Copyright (c) 2025 .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.Runtime.CompilerServices; + +namespace ReactiveUI; + +/// +/// Thread-safe registry for fallback binding converters using a lock-free snapshot pattern. +/// +/// +/// +/// This registry uses a copy-on-write snapshot pattern optimized for read-heavy workloads: +/// +/// +/// +/// Reads: Lock-free via a volatile read of the snapshot reference. +/// Multiple readers can access the registry concurrently without contention. +/// +/// +/// Writes: Serialized under a lock. Writes clone the converter list, +/// mutate the clone, and publish a new snapshot atomically. +/// +/// +/// Selection: Fallback converters are stored in a simple list (no type-pair grouping). +/// When looking up a converter, each converter's runtime affinity is checked via +/// . +/// The converter with the highest affinity (> 0) is selected. +/// +/// +/// +/// Fallback converters are used when no exact type-pair match is found in the typed converter registry. +/// They provide runtime type checking and conversion using techniques like reflection or type descriptors. +/// +/// +public sealed class BindingFallbackConverterRegistry +{ + /// + /// Synchronization primitive guarding mutations to the registry's internal state. + /// + /// + /// Protects updates to . Reads resolve from the snapshot without locking. + /// +#if NET9_0_OR_GREATER + private readonly Lock _gate = new(); +#else + private readonly object _gate = new(); +#endif + + /// + /// Stores all registered fallback converters. + /// + /// + /// This is a copy-on-write snapshot to allow lock-free reads: + /// writers publish a new instance via assignment under ; + /// readers use a volatile read without locking. + /// + private Snapshot? _snapshot; + + /// + /// Registers a fallback binding converter. + /// + /// The converter to register. Must not be null. + /// Thrown if is null. + /// + /// + /// Fallback converters are consulted when no exact type-pair converter is found. + /// Multiple fallback converters can be registered; when retrieved, the converter with + /// the highest affinity for the requested type pair will be selected. + /// + /// + /// This method is thread-safe but serialized (only one registration can occur at a time). + /// Reads can proceed concurrently with writes. + /// + /// + public void Register(IBindingFallbackConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(converter); + + lock (_gate) + { + var snap = _snapshot ?? new Snapshot(new List(8)); + + // Copy-on-write update: clone the list + var newList = new List(snap.Converters) { converter }; + + // Publish the new snapshot (atomic via reference assignment) + _snapshot = new Snapshot(newList); + } + } + + /// + /// Attempts to retrieve the best fallback converter for the specified type pair. + /// + /// The source type to convert from. + /// The target type to convert to. + /// + /// The converter with the highest affinity for the type pair, or if no converter supports the conversion. + /// + /// + /// Thrown if or is null. + /// + /// + /// + /// This method is lock-free and can be called concurrently from multiple threads. + /// It queries each registered fallback converter via + /// and returns the converter with the highest affinity (> 0). + /// + /// + /// If multiple converters return the same affinity, the last registered converter wins + /// (implementation detail based on iteration order). + /// + /// + public IBindingFallbackConverter? TryGetConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType) + { + ArgumentExceptionHelper.ThrowIfNull(fromType); + ArgumentExceptionHelper.ThrowIfNull(toType); + + var snap = Volatile.Read(ref _snapshot); + if (snap is null) + { + return null; + } + + // Find the converter with the highest affinity + IBindingFallbackConverter? best = null; + var bestScore = -1; + + var converters = snap.Converters; + for (var i = 0; i < converters.Count; i++) + { + var converter = converters[i]; + var score = converter.GetAffinityForObjects(fromType, toType); + if (score > bestScore && score > 0) + { + bestScore = score; + best = converter; + } + } + + return best; + } + + /// + /// Returns all registered fallback converters. + /// + /// + /// A sequence of all fallback converters currently registered in the registry. + /// Returns an empty sequence if no converters are registered. + /// + /// + /// This method is lock-free and returns a snapshot of all converters at the time of the call. + /// The returned sequence is safe to enumerate even if concurrent registrations occur. + /// + public IEnumerable GetAllConverters() + { + var snap = Volatile.Read(ref _snapshot); + if (snap is null) + { + return []; + } + + // Return a copy to avoid exposing internal list + return [.. snap.Converters]; + } + + /// + /// A copy-on-write snapshot of the fallback converter registry. + /// + /// + /// List of all registered fallback converters. + /// + /// + /// This record enables lock-free reads: readers access an immutable reference to the list, + /// while writers publish a new snapshot after applying mutations. + /// + private sealed record Snapshot(List Converters); +} diff --git a/src/ReactiveUI/Bindings/Converters/BindingTypeConverterRegistry.cs b/src/ReactiveUI/Bindings/Converters/BindingTypeConverterRegistry.cs new file mode 100644 index 0000000000..a20f3474ce --- /dev/null +++ b/src/ReactiveUI/Bindings/Converters/BindingTypeConverterRegistry.cs @@ -0,0 +1,237 @@ +// Copyright (c) 2025 .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.Runtime.CompilerServices; + +namespace ReactiveUI; + +/// +/// Thread-safe registry for typed binding converters using a lock-free snapshot pattern. +/// +/// +/// +/// This registry uses a copy-on-write snapshot pattern optimized for read-heavy workloads: +/// +/// +/// +/// Reads: Lock-free via a volatile read of the snapshot reference. +/// Multiple readers can access the registry concurrently without contention. +/// +/// +/// Writes: Serialized under a lock. Writes clone the affected dictionary entry, +/// mutate the clone, and publish a new snapshot atomically. +/// +/// +/// Selection: Converters are grouped by (FromType, ToType) pair. +/// When multiple converters match, the one with the highest affinity (> 0) is selected. +/// +/// +/// +/// This design prioritizes performance for the common case: converters are registered once at +/// application startup, then looked up many times during binding operations. +/// +/// +public sealed class BindingTypeConverterRegistry +{ +#if NET9_0_OR_GREATER + /// + /// Synchronization primitive guarding mutations to the registry's internal state. + /// + /// + /// Protects updates to . Reads resolve from the snapshot without locking. + /// + private readonly Lock _gate = new(); +#else + /// + /// Synchronization primitive guarding mutations to the registry's internal state. + /// + /// + /// Protects updates to . Reads resolve from the snapshot without locking. + /// + private readonly object _gate = new(); +#endif + + /// + /// Stores all registered converters grouped by (FromType, ToType) pair. + /// + /// + /// This is a copy-on-write snapshot to allow lock-free reads: + /// writers publish a new instance via assignment under ; + /// readers use a volatile read without locking. + /// + private Snapshot? _snapshot; + + /// + /// Registers a typed binding converter. + /// + /// The converter to register. Must not be null. + /// Thrown if is null. + /// + /// + /// Converters are grouped by their (FromType, ToType) pair. Multiple converters can be + /// registered for the same type pair; when retrieved, the converter with the highest + /// affinity (returned by ) will be selected. + /// + /// + /// This method is thread-safe but serialized (only one registration can occur at a time). + /// Reads can proceed concurrently with writes. + /// + /// + public void Register(IBindingTypeConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(converter); + + var key = (converter.FromType, converter.ToType); + + lock (_gate) + { + var snap = _snapshot ?? new Snapshot(new Dictionary<(Type fromType, Type toType), List>(16)); + + // Copy-on-write update: clone the dictionary shallowly + var newDict = CloneRegistryShallow(snap.ConvertersByTypePair); + + if (!newDict.TryGetValue(key, out var list)) + { + list = new List(4); + } + else + { + // Copy-on-write at the list level: clone before mutating + list = [.. list]; + } + + list.Add(converter); + newDict[key] = list; + + // Publish the new snapshot (atomic via reference assignment) + _snapshot = new Snapshot(newDict); + } + } + + /// + /// Attempts to retrieve the best converter for the specified type pair. + /// + /// The source type to convert from. + /// The target type to convert to. + /// + /// The converter with the highest affinity for the type pair, or if no converter is registered. + /// + /// + /// Thrown if or is null. + /// + /// + /// + /// This method is lock-free and can be called concurrently from multiple threads. + /// It returns the converter with the highest affinity (> 0) for the exact type pair. + /// + /// + /// If multiple converters are registered for the same type pair, the selection is based + /// on affinity returned by : + /// the converter with the highest score wins. + /// + /// + public IBindingTypeConverter? TryGetConverter(Type fromType, Type toType) + { + ArgumentExceptionHelper.ThrowIfNull(fromType); + ArgumentExceptionHelper.ThrowIfNull(toType); + + var snap = Volatile.Read(ref _snapshot); + if (snap is null) + { + return null; + } + + if (!snap.ConvertersByTypePair.TryGetValue((fromType, toType), out var list)) + { + return null; + } + + // Find the converter with the highest affinity + IBindingTypeConverter? best = null; + var bestScore = -1; + + for (var i = 0; i < list.Count; i++) + { + var converter = list[i]; + var score = converter.GetAffinityForObjects(); + if (score > bestScore && score > 0) + { + bestScore = score; + best = converter; + } + } + + return best; + } + + /// + /// Returns all registered converters. + /// + /// + /// A sequence of all converters currently registered in the registry. + /// Returns an empty sequence if no converters are registered. + /// + /// + /// This method is lock-free and returns a snapshot of all converters at the time of the call. + /// The returned sequence is safe to enumerate even if concurrent registrations occur. + /// + public IEnumerable GetAllConverters() + { + var snap = Volatile.Read(ref _snapshot); + if (snap is null) + { + return []; + } + + // Flatten all lists into a single enumerable + // Use a list to avoid lazy evaluation issues with concurrent modifications + var result = new List(); + foreach (var kvp in snap.ConvertersByTypePair) + { + result.AddRange(kvp.Value); + } + + return result; + } + + /// + /// Creates a shallow clone of a registry dictionary. + /// + /// The source dictionary to clone. + /// + /// A new dictionary instance containing the same keys and list references as . + /// + /// Thrown if is . + /// + /// Lists are cloned only when mutated (copy-on-write at the list level). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Dictionary<(Type fromType, Type toType), List> CloneRegistryShallow( + Dictionary<(Type fromType, Type toType), List> source) + { + ArgumentExceptionHelper.ThrowIfNull(source); + + var clone = new Dictionary<(Type fromType, Type toType), List>(source.Count); + foreach (var kvp in source) + { + clone[kvp.Key] = kvp.Value; + } + + return clone; + } + + /// + /// A copy-on-write snapshot of the converter registry. + /// + /// + /// Dictionary of converters grouped by (FromType, ToType) pair. + /// + /// + /// This record enables lock-free reads: readers access an immutable reference to the dictionary, + /// while writers publish a new snapshot after applying mutations. + /// + private sealed record Snapshot( + Dictionary<(Type fromType, Type toType), List> ConvertersByTypePair); +} diff --git a/src/ReactiveUI/Bindings/Converters/ConverterMigrationHelper.cs b/src/ReactiveUI/Bindings/Converters/ConverterMigrationHelper.cs new file mode 100644 index 0000000000..92bc9664a3 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converters/ConverterMigrationHelper.cs @@ -0,0 +1,184 @@ +// Copyright (c) 2025 .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 helper methods for migrating converters from Splat to the new . +/// +/// +/// +/// This class assists with migrating from the legacy Splat-based converter registration +/// to the new -based system introduced in ReactiveUI v20. +/// +/// +/// When to Use: +/// +/// +/// +/// You have existing code that registers converters directly with Splat's . +/// +/// +/// You want to preserve existing Splat-registered converters while migrating to the new system. +/// +/// +/// You need to extract converter instances for inspection or manual registration. +/// +/// +/// +/// Migration Strategies: +/// +/// +/// +/// Automatic (Recommended): Use the ReactiveUIBuilder.WithConvertersFrom() method +/// to automatically import all Splat-registered converters during application initialization. +/// +/// +/// Manual Extraction: Use to get all converters +/// from Splat, then inspect or register them manually. +/// +/// +/// Direct Import: Use to import converters directly +/// into an existing instance. +/// +/// +/// +/// +/// +/// Example 1: Automatic migration via builder (recommended) +/// +/// +/// // Import all existing Splat-registered converters automatically +/// RxAppBuilder.CreateReactiveUIBuilder() +/// .WithConvertersFrom(AppLocator.Current) +/// .WithConverter(new AdditionalConverter()) // Add new converters +/// .BuildApp(); +/// +/// +/// Example 2: Manual extraction and inspection +/// +/// +/// // Extract converters for inspection +/// var (typed, fallback, setMethod) = ConverterMigrationHelper.ExtractConverters(AppLocator.Current); +/// +/// Console.WriteLine($"Found {typed.Count} typed converters"); +/// Console.WriteLine($"Found {fallback.Count} fallback converters"); +/// Console.WriteLine($"Found {setMethod.Count} set-method converters"); +/// +/// // Register them individually if needed +/// var converterService = new ConverterService(); +/// foreach (var converter in typed) +/// { +/// converterService.TypedConverters.Register(converter); +/// } +/// +/// +/// Example 3: Direct import into existing service +/// +/// +/// var converterService = RxConverters.Current; +/// converterService.ImportFrom(AppLocator.Current); +/// +/// +public static class ConverterMigrationHelper +{ + /// + /// Extracts all converters from a Splat dependency resolver. + /// + /// The Splat resolver to extract converters from. Must not be null. + /// + /// A tuple containing lists of typed converters, fallback converters, and set-method converters. + /// + /// Thrown if is null. + /// + /// + /// This method queries the Splat resolver for all registered converters of each type + /// and returns them as separate lists. Null converter instances are filtered out. + /// + /// + /// The returned lists are new instances - modifying them does not affect the resolver. + /// + /// + /// + /// + /// var (typed, fallback, setMethod) = ConverterMigrationHelper.ExtractConverters(AppLocator.Current); + /// Console.WriteLine($"Found {typed.Count} typed converters"); + /// + /// + public static ( + IList TypedConverters, + IList FallbackConverters, + IList SetMethodConverters) + ExtractConverters(IReadonlyDependencyResolver resolver) + { + ArgumentExceptionHelper.ThrowIfNull(resolver); + + var typed = new List( + resolver.GetServices().Where(static c => c is not null)!); + + var fallback = new List( + resolver.GetServices().Where(static c => c is not null)!); + + var setMethod = new List( + resolver.GetServices().Where(static c => c is not null)!); + + return (typed, fallback, setMethod); + } + + /// + /// Imports converters from a Splat resolver directly into a . + /// + /// The converter service to import into. Must not be null. + /// The Splat resolver to import converters from. Must not be null. + /// + /// Thrown if or is null. + /// + /// + /// + /// This extension method extracts all converters from the Splat resolver and registers them + /// with the specified . This is useful for migrating existing + /// Splat-based converter registrations to the new system. + /// + /// + /// Important: This method imports converters at the time it's called. + /// Any converters registered with Splat after this call will not be included. + /// + /// + /// + /// + /// // Import all Splat-registered converters into the current service + /// var converterService = RxConverters.Current; + /// converterService.ImportFrom(AppLocator.Current); + /// + /// // Or create a new service and import into it + /// var newService = new ConverterService(); + /// newService.ImportFrom(AppLocator.Current); + /// + /// + public static void ImportFrom( + this ConverterService converterService, + IReadonlyDependencyResolver resolver) + { + ArgumentExceptionHelper.ThrowIfNull(converterService); + ArgumentExceptionHelper.ThrowIfNull(resolver); + + var (typed, fallback, setMethod) = ExtractConverters(resolver); + + foreach (var converter in typed) + { + converterService.TypedConverters.Register(converter); + } + + foreach (var converter in fallback) + { + converterService.FallbackConverters.Register(converter); + } + + foreach (var converter in setMethod) + { + converterService.SetMethodConverters.Register(converter); + } + } +} diff --git a/src/ReactiveUI/Bindings/Converters/ConverterService.cs b/src/ReactiveUI/Bindings/Converters/ConverterService.cs new file mode 100644 index 0000000000..07581a86a8 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converters/ConverterService.cs @@ -0,0 +1,189 @@ +// Copyright (c) 2025 .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 unified access to all converter registries in ReactiveUI. +/// +/// +/// +/// This service manages three types of converters: +/// +/// +/// +/// Typed Converters: Exact type-pair converters (e.g., int → string). +/// Registered via and selected based on affinity for exact matches. +/// +/// +/// Fallback Converters: Runtime type converters using reflection or type descriptors. +/// Registered via and consulted when no typed converter matches. +/// +/// +/// Set-Method Converters: Specialized converters for binding set operations. +/// Registered via for platform-specific control binding. +/// +/// +/// +/// Converter Selection Algorithm: +/// +/// +/// +/// Phase 1: Search for exact type-pair match in . +/// If found, return the typed converter with the highest affinity. +/// +/// +/// Phase 2: If no typed converter found, search . +/// Return the fallback converter with the highest affinity for the runtime types. +/// +/// +/// Result: If neither phase finds a converter, return . +/// +/// +/// +/// Affinity Guidelines: +/// +/// +/// 0: Converter cannot handle the type pair +/// 1: Last resort (e.g., EqualityTypeConverter) +/// 2: Standard ReactiveUI converters (string, numeric, datetime) +/// 8: Platform-specific standard converters (NSDate, WinForms controls) +/// 100+: Third-party override range (use to override ReactiveUI defaults) +/// +/// +public sealed class ConverterService +{ + /// + /// Initializes a new instance of the class. + /// + public ConverterService() + { + TypedConverters = new BindingTypeConverterRegistry(); + FallbackConverters = new BindingFallbackConverterRegistry(); + SetMethodConverters = new SetMethodBindingConverterRegistry(); + } + + /// + /// Gets the registry for typed binding converters. + /// + /// + /// The typed converter registry for exact type-pair conversions. + /// + /// + /// Use this registry to register and retrieve converters for specific (FromType, ToType) pairs. + /// Typed converters are consulted first during binding operations and provide the best performance. + /// + public BindingTypeConverterRegistry TypedConverters { get; } + + /// + /// Gets the registry for fallback binding converters. + /// + /// + /// The fallback converter registry for runtime type conversions. + /// + /// + /// Use this registry to register and retrieve converters that use runtime type checking. + /// Fallback converters are consulted only when no exact type-pair converter is found. + /// These typically use reflection or System.ComponentModel.TypeDescriptor for conversion. + /// + public BindingFallbackConverterRegistry FallbackConverters { get; } + + /// + /// Gets the registry for set-method binding converters. + /// + /// + /// The set-method converter registry for specialized set operations. + /// + /// + /// Use this registry to register and retrieve converters for platform-specific binding set operations, + /// such as populating collection controls or handling specialized platform widgets. + /// + public SetMethodBindingConverterRegistry SetMethodConverters { get; } + + /// + /// Resolves the best converter for the specified type pair. + /// + /// The source type to convert from. + /// The target type to convert to. + /// + /// The best converter for the type pair (either typed or fallback), or if no converter is available. + /// + /// + /// Thrown if or is null. + /// + /// + /// + /// This method implements the two-phase converter selection algorithm: + /// + /// + /// + /// Phase 1: Try to find an exact type-pair match in . + /// If found, return the typed converter with the highest affinity. + /// + /// + /// Phase 2: If no typed converter found, search . + /// Return the fallback converter with the highest affinity for the runtime types. + /// + /// + /// + /// This method is thread-safe and lock-free, making it safe to call from multiple threads concurrently. + /// + /// + /// + /// + /// var converter = converterService.ResolveConverter(typeof(int), typeof(string)); + /// if (converter is IBindingTypeConverter typedConverter) + /// { + /// // Use typed converter + /// } + /// else if (converter is IBindingFallbackConverter fallbackConverter) + /// { + /// // Use fallback converter + /// } + /// + /// + public object? ResolveConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType) + { + ArgumentExceptionHelper.ThrowIfNull(fromType); + ArgumentExceptionHelper.ThrowIfNull(toType); + + // Phase 1: Try exact type-pair match (typed converters) + var typed = TypedConverters.TryGetConverter(fromType, toType); + if (typed is not null) + { + return typed; + } + + // Phase 2: Try fallback converters (runtime type checking) + var fallback = FallbackConverters.TryGetConverter(fromType, toType); + return fallback; + } + + /// + /// Resolves the best set-method converter for the specified type pair. + /// + /// The source type to convert from. May be null. + /// The target type to convert to. May be null. + /// + /// The best set-method converter for the type pair, or if no converter is available. + /// + /// + /// + /// This method queries all registered set-method converters and returns the one with the + /// highest affinity (> 0) for the specified type pair. + /// + /// + /// This method is thread-safe and lock-free, making it safe to call from multiple threads concurrently. + /// + /// + public ISetMethodBindingConverter? ResolveSetMethodConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? fromType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? toType) + { + return SetMethodConverters.TryGetConverter(fromType, toType); + } +} diff --git a/src/ReactiveUI/Bindings/Converters/RxConverters.cs b/src/ReactiveUI/Bindings/Converters/RxConverters.cs new file mode 100644 index 0000000000..d76908f022 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converters/RxConverters.cs @@ -0,0 +1,119 @@ +// Copyright (c) 2025 .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; + +namespace ReactiveUI; + +/// +/// Provides static access to the ReactiveUI converter service. +/// +/// +/// +/// This class provides a global access point to the instance +/// used by ReactiveUI for binding type conversions. It is initialized during application +/// startup via the pattern. +/// +/// +/// Recommended Usage: +/// +/// +/// In most cases, you don't need to access converters directly - the binding system +/// uses them automatically. However, if you need to manually resolve a converter or +/// register custom converters after initialization, use this class. +/// +/// +/// Initialization: +/// +/// +/// The converter service is initialized when you call ReactiveUIBuilder.BuildApp(): +/// +/// +/// RxAppBuilder.CreateReactiveUIBuilder() +/// .WithCoreServices() +/// .WithPlatformServices() +/// .BuildApp(); +/// +/// +/// Custom Converter Registration: +/// +/// +/// Register custom converters during builder configuration (recommended): +/// +/// +/// RxAppBuilder.CreateReactiveUIBuilder() +/// .WithConverter(new MyCustomConverter()) +/// .BuildApp(); +/// +/// +/// Or register after initialization (not recommended, but supported): +/// +/// +/// RxConverters.Current.TypedConverters.Register(new MyCustomConverter()); +/// +/// +/// +/// +/// Example: Manually resolving a converter +/// +/// +/// var converter = RxConverters.Current.ResolveConverter(typeof(int), typeof(string)); +/// if (converter is IBindingTypeConverter<int, string> typedConverter) +/// { +/// if (typedConverter.TryConvert(42, null, out var result)) +/// { +/// Console.WriteLine(result); // "42" +/// } +/// } +/// +/// +public static class RxConverters +{ + /// + /// Backing field for the converter service. + /// + private static ConverterService _current = new(); + + /// + /// Gets the current converter service instance. + /// + /// + /// The instance used by ReactiveUI. + /// + /// + /// + /// This property provides access to the converter service for manual converter + /// resolution or registration. In most cases, you don't need to use this directly + /// as the binding system handles converter selection automatically. + /// + /// + /// The service is initialized during application startup via . + /// If you access this property before calling BuildApp(), you'll get an empty + /// service with no converters registered. + /// + /// + public static ConverterService Current => _current; + + /// + /// Sets the converter service instance. + /// + /// The converter service to use. Must not be null. + /// Thrown if is null. + /// + /// + /// This method is called internally by during application + /// initialization. Application code should not call this method directly. + /// + /// + /// For Testing: Unit tests can call this method to inject a test service + /// with mock converters, but should restore the original service after the test completes. + /// + /// + internal static void SetService(ConverterService service) + { + ArgumentExceptionHelper.ThrowIfNull(service); + _current = service; + } +} diff --git a/src/ReactiveUI/Bindings/Converters/SetMethodBindingConverterRegistry.cs b/src/ReactiveUI/Bindings/Converters/SetMethodBindingConverterRegistry.cs new file mode 100644 index 0000000000..fbe8467b26 --- /dev/null +++ b/src/ReactiveUI/Bindings/Converters/SetMethodBindingConverterRegistry.cs @@ -0,0 +1,182 @@ +// Copyright (c) 2025 .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.Runtime.CompilerServices; + +namespace ReactiveUI; + +/// +/// Thread-safe registry for set-method binding converters using a lock-free snapshot pattern. +/// +/// +/// +/// This registry uses a copy-on-write snapshot pattern optimized for read-heavy workloads: +/// +/// +/// +/// Reads: Lock-free via a volatile read of the snapshot reference. +/// Multiple readers can access the registry concurrently without contention. +/// +/// +/// Writes: Serialized under a lock. Writes clone the converter list, +/// mutate the clone, and publish a new snapshot atomically. +/// +/// +/// Selection: Set-method converters are stored in a simple list (no type-pair grouping). +/// When looking up a converter, each converter's runtime affinity is checked via +/// . +/// The converter with the highest affinity (> 0) is selected. +/// +/// +/// +/// Set-method converters are used for specialized binding operations that require custom +/// set behavior, such as populating collections or handling platform-specific controls. +/// +/// +public sealed class SetMethodBindingConverterRegistry +{ +#if NET9_0_OR_GREATER + /// + /// Synchronization primitive guarding mutations to the registry's internal state. + /// + /// + /// Protects updates to . Reads resolve from the snapshot without locking. + /// + private readonly Lock _gate = new(); +#else + /// + /// Synchronization primitive guarding mutations to the registry's internal state. + /// + /// + /// Protects updates to . Reads resolve from the snapshot without locking. + /// + private readonly object _gate = new(); +#endif + + /// + /// Stores all registered set-method converters. + /// + /// + /// This is a copy-on-write snapshot to allow lock-free reads: + /// writers publish a new instance via assignment under ; + /// readers use a volatile read without locking. + /// + private Snapshot? _snapshot; + + /// + /// Registers a set-method binding converter. + /// + /// The converter to register. Must not be null. + /// Thrown if is null. + /// + /// + /// Set-method converters provide specialized behavior for binding set operations. + /// Multiple converters can be registered; when retrieved, the converter with + /// the highest affinity for the requested type pair will be selected. + /// + /// + /// This method is thread-safe but serialized (only one registration can occur at a time). + /// Reads can proceed concurrently with writes. + /// + /// + public void Register(ISetMethodBindingConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(converter); + + lock (_gate) + { + var snap = _snapshot ?? new Snapshot(new List(8)); + + // Copy-on-write update: clone the list + var newList = new List(snap.Converters) { converter }; + + // Publish the new snapshot (atomic via reference assignment) + _snapshot = new Snapshot(newList); + } + } + + /// + /// Attempts to retrieve the best set-method converter for the specified type pair. + /// + /// The source type to convert from. May be null. + /// The target type to convert to. May be null. + /// + /// The converter with the highest affinity for the type pair, or if no converter supports the conversion. + /// + /// + /// + /// This method is lock-free and can be called concurrently from multiple threads. + /// It queries each registered set-method converter via + /// and returns the converter with the highest affinity (> 0). + /// + /// + /// If multiple converters return the same affinity, the last registered converter wins + /// (implementation detail based on iteration order). + /// + /// + public ISetMethodBindingConverter? TryGetConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? fromType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? toType) + { + var snap = Volatile.Read(ref _snapshot); + if (snap is null) + { + return null; + } + + // Find the converter with the highest affinity + ISetMethodBindingConverter? best = null; + var bestScore = -1; + + var converters = snap.Converters; + for (var i = 0; i < converters.Count; i++) + { + var converter = converters[i]; + var score = converter.GetAffinityForObjects(fromType, toType); + if (score > bestScore && score > 0) + { + bestScore = score; + best = converter; + } + } + + return best; + } + + /// + /// Returns all registered set-method converters. + /// + /// + /// A sequence of all set-method converters currently registered in the registry. + /// Returns an empty sequence if no converters are registered. + /// + /// + /// This method is lock-free and returns a snapshot of all converters at the time of the call. + /// The returned sequence is safe to enumerate even if concurrent registrations occur. + /// + public IEnumerable GetAllConverters() + { + var snap = Volatile.Read(ref _snapshot); + if (snap is null) + { + return []; + } + + // Return a copy to avoid exposing internal list + return [.. snap.Converters]; + } + + /// + /// A copy-on-write snapshot of the set-method converter registry. + /// + /// + /// List of all registered set-method converters. + /// + /// + /// This record enables lock-free reads: readers access an immutable reference to the list, + /// while writers publish a new snapshot after applying mutations. + /// + private sealed record Snapshot(List Converters); +} diff --git a/src/ReactiveUI/Bindings/IBindingFallbackConverter.cs b/src/ReactiveUI/Bindings/IBindingFallbackConverter.cs new file mode 100644 index 0000000000..d78687df5f --- /dev/null +++ b/src/ReactiveUI/Bindings/IBindingFallbackConverter.cs @@ -0,0 +1,87 @@ +// Copyright (c) 2025 .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; + +/// +/// Represents a converter that can handle runtime type pairs not covered by typed converters. +/// Fallback converters are consulted only after all typed converters fail to match. +/// +/// +/// +/// Fallback converters exist for scenarios where conversion logic depends on runtime type +/// characteristics that cannot be expressed as compile-time generic pairs. +/// +/// +/// Common use cases: +/// +/// Component model type descriptors (reflection-based) +/// Platform-specific type conversions +/// IConvertible-based conversions +/// +/// +/// +/// Affinity Guidelines: +/// +/// 0 = Not supported +/// 1 = Last resort (ComponentModel/TypeDescriptor) +/// 3 = Broad runtime conversion (IConvertible/numeric widening) +/// 5 = Strong structural match (enum parsing, nullable unwrapping) +/// +/// Do not return affinity ≥10 to avoid competing with typed converters. +/// +/// +public interface IBindingFallbackConverter : IEnableLogger +{ + /// + /// Calculates affinity for the specified runtime type pair. + /// + /// The runtime source type. + /// The target type. + /// + /// Affinity score (0-5 range). Higher values indicate stronger match. + /// Return 0 or negative if this converter cannot handle the pair. + /// + /// + /// + /// This method MUST be: + /// + /// Pure (no side effects) + /// Fast (cache any expensive metadata) + /// Safe (no exceptions, no user code execution) + /// + /// + /// + /// This method is invoked during converter selection and may be called frequently. + /// Results should be cached internally where appropriate. + /// + /// + int GetAffinityForObjects([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType); + + /// + /// Attempts to convert the value to the target type. + /// + /// The runtime source type (guaranteed non-null). + /// The value to convert (guaranteed non-null). + /// The target type (guaranteed non-null). + /// Implementation-defined conversion hint (e.g., format string, culture). + /// + /// The converted value. Guaranteed non-null when this method returns . + /// + /// if conversion succeeded; otherwise, . + /// + /// + /// When this method returns , the parameter + /// is guaranteed to be non-null (modern nullability contract). + /// + /// + /// Null input handling is performed by the dispatch layer. This method will never receive + /// null as the parameter. + /// + /// + bool TryConvert([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, object from, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType, object? conversionHint, [NotNullWhen(true)] out object? result); +} diff --git a/src/ReactiveUI/Bindings/IBindingTypeConverter.cs b/src/ReactiveUI/Bindings/IBindingTypeConverter.cs index e5c19d764b..e26296508f 100644 --- a/src/ReactiveUI/Bindings/IBindingTypeConverter.cs +++ b/src/ReactiveUI/Bindings/IBindingTypeConverter.cs @@ -12,6 +12,16 @@ namespace ReactiveUI; /// public interface IBindingTypeConverter : IEnableLogger { + /// + /// Gets the source type supported by this converter. + /// + Type FromType { get; } + + /// + /// Gets the target type supported by this converter. + /// + Type ToType { get; } + /// /// Returns a positive integer when this class supports /// TryConvert for this particular Type. If the method isn't supported at @@ -19,20 +29,16 @@ public interface IBindingTypeConverter : IEnableLogger /// return a positive value, the host will use the one which returns /// the highest value. When in doubt, return '2' or '0'. /// - /// The source type to convert from. - /// The target type to convert to. /// A positive integer if TryConvert is supported, /// zero or a negative value otherwise. - int GetAffinityForObjects(Type fromType, Type toType); + int GetAffinityForObjects(); /// - /// Convert a given object to the specified type. + /// Attempts to convert using the typed implementation, exposed via an object-based shim. /// - /// The object to convert. - /// The type to coerce the object to. - /// An implementation-defined value, - /// usually to specify things like locale awareness. - /// An object that is of the type . - /// True if conversion was successful. - bool TryConvert(object? from, Type toType, object? conversionHint, out object? result); + /// The source value. + /// Implementation-defined hint. + /// The converted value. + /// if conversion succeeded; otherwise . + bool TryConvertTyped(object? from, object? conversionHint, out object? result); } diff --git a/src/ReactiveUI/Bindings/IBindingTypeConverter{TFrom,TTo}.cs b/src/ReactiveUI/Bindings/IBindingTypeConverter{TFrom,TTo}.cs new file mode 100644 index 0000000000..8f10643102 --- /dev/null +++ b/src/ReactiveUI/Bindings/IBindingTypeConverter{TFrom,TTo}.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2025 .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; + +/// +/// Generic type-safe interface for converting between specific types. +/// Implement this alongside for AOT-safe conversions. +/// +/// The source type to convert from. +/// The target type to convert to. +/// +/// +/// This interface provides compile-time type safety for type conversions, +/// enabling AOT-compatible code paths that avoid boxing and reflection. +/// +/// +/// The generic method +/// is preferred over the base interface's object-based method when types +/// are known at compile time. +/// +/// +public interface IBindingTypeConverter : IBindingTypeConverter +{ + /// + /// Convert a value to the target type in a type-safe manner. + /// + /// The value to convert. + /// Implementation-defined hint for conversion (e.g., format string, locale). + /// The converted value. May be when conversion succeeds for nullable targets. + /// if conversion succeeded; otherwise, . + /// + /// + /// This method is AOT-safe as all types are known at compile time. + /// No reflection or boxing is required for value types. + /// + /// + /// When this method returns , the parameter + /// may still be for converters that map null inputs or empty values + /// to nullable targets. + /// + /// + bool TryConvert(TFrom? from, object? conversionHint, [MaybeNullWhen(true)] out TTo? result); +} diff --git a/src/ReactiveUI/Bindings/ISetMethodBindingConverter.cs b/src/ReactiveUI/Bindings/ISetMethodBindingConverter.cs index c9ae1c2e79..06702cbdfc 100644 --- a/src/ReactiveUI/Bindings/ISetMethodBindingConverter.cs +++ b/src/ReactiveUI/Bindings/ISetMethodBindingConverter.cs @@ -21,10 +21,6 @@ public interface ISetMethodBindingConverter : IEnableLogger /// The target type to convert to. /// A positive integer if PerformSet is supported, /// zero or a negative value otherwise. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] -#endif int GetAffinityForObjects(Type? fromType, Type? toType); /// diff --git a/src/ReactiveUI/Bindings/Interaction/IInteractionBinderImplementation.cs b/src/ReactiveUI/Bindings/Interaction/IInteractionBinderImplementation.cs index 388d6021eb..c9ee166c19 100644 --- a/src/ReactiveUI/Bindings/Interaction/IInteractionBinderImplementation.cs +++ b/src/ReactiveUI/Bindings/Interaction/IInteractionBinderImplementation.cs @@ -22,10 +22,7 @@ public interface IInteractionBinderImplementation : IEnableLogger /// The interaction's input type. /// The interaction's output type. /// An object that when disposed, disconnects the binding. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindInteraction uses expression evaluation which requires dynamic code generation")] - [RequiresUnreferencedCode("BindInteraction uses expression evaluation which may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] IDisposable BindInteraction( TViewModel? viewModel, TView view, @@ -47,10 +44,7 @@ IDisposable BindInteraction( /// The interaction's output type. /// The interaction's signal type. /// An object that when disposed, disconnects the binding. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindInteraction uses expression evaluation which requires dynamic code generation")] - [RequiresUnreferencedCode("BindInteraction uses expression evaluation which may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] IDisposable BindInteraction( TViewModel? viewModel, TView view, diff --git a/src/ReactiveUI/Bindings/Interaction/InteractionBinderImplementation.cs b/src/ReactiveUI/Bindings/Interaction/InteractionBinderImplementation.cs index cdad94e97e..5d67d4609d 100644 --- a/src/ReactiveUI/Bindings/Interaction/InteractionBinderImplementation.cs +++ b/src/ReactiveUI/Bindings/Interaction/InteractionBinderImplementation.cs @@ -11,10 +11,7 @@ namespace ReactiveUI; public class InteractionBinderImplementation : IInteractionBinderImplementation { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public IDisposable BindInteraction( TViewModel? viewModel, TView view, @@ -44,10 +41,7 @@ public IDisposable BindInteraction( } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public IDisposable BindInteraction( TViewModel? viewModel, TView view, diff --git a/src/ReactiveUI/Bindings/Interaction/InteractionBindingMixins.cs b/src/ReactiveUI/Bindings/Interaction/InteractionBindingMixins.cs index 6443b78375..9690138ed7 100644 --- a/src/ReactiveUI/Bindings/Interaction/InteractionBindingMixins.cs +++ b/src/ReactiveUI/Bindings/Interaction/InteractionBindingMixins.cs @@ -30,8 +30,6 @@ public static class InteractionBindingMixins { private static readonly InteractionBinderImplementation _binderImplementation = new(); - static InteractionBindingMixins() => RxApp.EnsureInitialized(); - /// /// Binds the on a ViewModel to the specified handler. /// @@ -44,10 +42,7 @@ public static class InteractionBindingMixins /// The interaction's input type. /// The interaction's output type. /// An object that when disposed, disconnects the binding. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindInteraction uses expression binding which requires dynamic code generation")] - [RequiresUnreferencedCode("BindInteraction uses expression binding which may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static IDisposable BindInteraction( this TView view, TViewModel? viewModel, @@ -74,10 +69,7 @@ public static IDisposable BindInteraction( /// The interaction's output type. /// The interaction's signal type. /// An object that when disposed, disconnects the binding. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindInteraction uses expression binding which requires dynamic code generation")] - [RequiresUnreferencedCode("BindInteraction uses expression binding which may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static IDisposable BindInteraction( this TView view, TViewModel? viewModel, diff --git a/src/ReactiveUI/Bindings/Property/IPropertyBinderImplementation.cs b/src/ReactiveUI/Bindings/Property/IPropertyBinderImplementation.cs index bff02b55e9..436a93c377 100644 --- a/src/ReactiveUI/Bindings/Property/IPropertyBinderImplementation.cs +++ b/src/ReactiveUI/Bindings/Property/IPropertyBinderImplementation.cs @@ -47,10 +47,6 @@ public interface IPropertyBinderImplementation : IEnableLogger /// An instance of that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Bind uses expression trees which require dynamic code generation")] - [RequiresUnreferencedCode("Bind uses expression trees which may require unreferenced code")] -#endif IReactiveBinding Bind( TViewModel? viewModel, TView view, @@ -98,10 +94,6 @@ public interface IPropertyBinderImplementation : IEnableLogger /// An instance of that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Bind uses expression trees which require dynamic code generation")] - [RequiresUnreferencedCode("Bind uses expression trees which may require unreferenced code")] -#endif IReactiveBinding Bind( TViewModel? viewModel, TView view, @@ -153,10 +145,6 @@ public interface IPropertyBinderImplementation : IEnableLogger /// /// There is no registered converter from to . /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("OneWayBind uses expression trees which require dynamic code generation")] - [RequiresUnreferencedCode("OneWayBind uses expression trees which may require unreferenced code")] -#endif IReactiveBinding OneWayBind( TViewModel? viewModel, TView view, @@ -198,10 +186,6 @@ IReactiveBinding OneWayBind( /// An instance of that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("OneWayBind uses expression trees which require dynamic code generation")] - [RequiresUnreferencedCode("OneWayBind uses expression trees which may require unreferenced code")] -#endif IReactiveBinding OneWayBind( TViewModel? viewModel, TView view, @@ -235,10 +219,6 @@ IReactiveBinding OneWayBind( /// viewModel to view property. /// /// An object that when disposed, disconnects the binding. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindTo uses expression trees which require dynamic code generation")] - [RequiresUnreferencedCode("BindTo uses expression trees which may require unreferenced code")] -#endif IDisposable BindTo( IObservable observedChange, TTarget? target, diff --git a/src/ReactiveUI/Bindings/Property/PropertyBinderImplementation.cs b/src/ReactiveUI/Bindings/Property/PropertyBinderImplementation.cs index 3b0991ad27..5bd14c3850 100644 --- a/src/ReactiveUI/Bindings/Property/PropertyBinderImplementation.cs +++ b/src/ReactiveUI/Bindings/Property/PropertyBinderImplementation.cs @@ -3,45 +3,54 @@ // 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 System.Reflection; +using System.Runtime.CompilerServices; namespace ReactiveUI; /// /// Provides methods to bind properties to observables. /// +/// +/// +/// This implementation uses dynamic expression parsing and reflection to support arbitrary property chains and dynamic +/// observation (WhenAnyDynamic). As such, it is not trimming- or AOT-friendly without additional preservation. +/// +/// +/// Trimming/AOT: this type is annotated because it performs reflection over runtime types and expression graphs that may +/// be trimmed, and may invoke members that are annotated for dynamic code. +/// +/// +[RequiresUnreferencedCode("Uses reflection over runtime types and expression graphs which may be trimmed.")] +[RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflection-based invocation.")] public class PropertyBinderImplementation : IPropertyBinderImplementation { - private static readonly MemoizingMRUCache<(Type fromType, Type toType), IBindingTypeConverter?> _typeConverterCache = new( - (types, _) => AppLocator.Current.GetServices() - .Aggregate((currentAffinity: -1, currentBinding: default(IBindingTypeConverter)), (acc, x) => - { - var score = x?.GetAffinityForObjects(types.fromType, types.toType) ?? -1; - return score > acc.currentAffinity && score > 0 ? (score, x) : acc; - }).currentBinding, - RxApp.SmallCacheLimit); - - [SuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Marked as Preserve")] - [SuppressMessage("Trimming", "IL2026:Calling members annotated with 'RequiresUnreferencedCodeAttribute' may break functionality when trimming application code.", Justification = "Marked as Preserve")] - private static readonly MemoizingMRUCache<(Type? fromType, Type? toType), ISetMethodBindingConverter?> _setMethodCache = new( - (type, _) => AppLocator.Current.GetServices() - .Aggregate((currentAffinity: -1, currentBinding: default(ISetMethodBindingConverter)), (acc, x) => - { - var score = x.GetAffinityForObjects(type.fromType, type.toType); - return score > acc.currentAffinity && score > 0 ? (score, x) : acc; - }).currentBinding, - RxApp.SmallCacheLimit); - - static PropertyBinderImplementation() => RxApp.EnsureInitialized(); - + /// + /// Caches the best set-method converter for a given (fromType, toType) pair. + /// + /// + /// The cached value is the selected instance, or if none matches. + /// + private static readonly System.Collections.Concurrent.ConcurrentDictionary<(Type fromType, Type? toType), ISetMethodBindingConverter?> _setMethodCache = new(); + + /// + /// Initializes static members of the class. + /// Ensures ReactiveUI static initialization is performed before bindings are used. + /// + + /// + /// Represents a converter that attempts conversion and returns success via an parameter. + /// + /// The input value type. + /// The output value type. + /// The input value. + /// The converted output value. + /// if conversion succeeded; otherwise . private delegate bool OutFunc(T1 t1, out T2 t2); /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] -#endif public IReactiveBinding Bind( TViewModel? viewModel, TView view, @@ -57,39 +66,69 @@ public class PropertyBinderImplementation : IPropertyBinderImplementation { ArgumentExceptionHelper.ThrowIfNull(vmProperty); ArgumentExceptionHelper.ThrowIfNull(viewProperty); - var vmToViewConverter = vmToViewConverterOverride ?? GetConverterForTypes(typeof(TVMProp), typeof(TVProp)); - var viewToVMConverter = viewToVMConverterOverride ?? GetConverterForTypes(typeof(TVProp), typeof(TVMProp)); - if (vmToViewConverter is null || viewToVMConverter is null) + // First, try to find registered converters (prioritize user-registered converters) + // If an override is provided, use it; otherwise fall back to service locator + var vmToViewConverterObj = vmToViewConverterOverride ?? GetConverterForTypes(typeof(TVMProp), typeof(TVProp)); + + var viewToVMConverterObj = viewToVMConverterOverride ?? GetConverterForTypes(typeof(TVProp), typeof(TVMProp)); + + // Check if we have converters or if types are assignable + var hasConverters = vmToViewConverterObj is not null && viewToVMConverterObj is not null; + var typesAreAssignable = typeof(TVProp).IsAssignableFrom(typeof(TVMProp)) && typeof(TVMProp).IsAssignableFrom(typeof(TVProp)); + + if (!hasConverters && !typesAreAssignable) { throw new ArgumentException( - $"Can't two-way convert between {typeof(TVMProp)} and {typeof(TVProp)}. To fix this, register a IBindingTypeConverter or call the version with the converter Func."); + $"Can't two-way convert between {typeof(TVMProp)} and {typeof(TVProp)}. To fix this, register a IBindingTypeConverter or call the version with the converter Func."); } bool VmToViewFunc(TVMProp? vmValue, out TVProp vValue) { - var result = vmToViewConverter.TryConvert(vmValue, typeof(TVProp), conversionHint, out var tmp); + if (vmToViewConverterObj is not null) + { + var result = BindingTypeConverterDispatch.TryConvertAny( + vmToViewConverterObj, + typeof(TVMProp), + vmValue, + typeof(TVProp), + conversionHint, + out var tmp); + + vValue = result ? (TVProp)tmp! : default!; + return result; + } - vValue = result && tmp is not null ? (TVProp)tmp : default!; - return result; + // No converter - direct assignment + vValue = vmValue is TVProp typedValue ? typedValue : default!; + return true; } bool ViewToVmFunc(TVProp vValue, out TVMProp? vmValue) { - var result = viewToVMConverter.TryConvert(vValue, typeof(TVMProp?), conversionHint, out var tmp); + if (viewToVMConverterObj is not null) + { + var result = BindingTypeConverterDispatch.TryConvertAny( + viewToVMConverterObj, + typeof(TVProp), + vValue, + typeof(TVMProp?), + conversionHint, + out var tmp); + + vmValue = result ? (TVMProp?)tmp : default; + return result; + } - vmValue = result && tmp is not null ? (TVMProp?)tmp : default; - return result; + // No converter - direct assignment + vmValue = vValue is TVMProp typedValue ? typedValue : default; + return true; } return BindImpl(viewModel, view, vmProperty, viewProperty, signalViewUpdate, VmToViewFunc, ViewToVmFunc, triggerUpdate); } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] -#endif public IReactiveBinding Bind( TViewModel? viewModel, TView view, @@ -123,10 +162,6 @@ bool ViewToVmFunc(TVProp vValue, out TVMProp? vmValue) } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] -#endif public IReactiveBinding OneWayBind( TViewModel? viewModel, TView view, @@ -143,26 +178,50 @@ public IReactiveBinding OneWayBind(view, viewExpression, vmExpression, Observable.Empty(), BindingDirection.OneWay, Disposable.Empty); } - var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression) - .SelectMany(x => !converter.TryConvert(x, viewType, conversionHint, out var tmp) ? Observable.Empty : Observable.Return(tmp)); + // First, try to find a registered converter (prioritize user-registered converters) + var converterObj = vmToViewConverterOverride ?? GetConverterForTypes(typeof(TVMProp?), viewType); - var (disposable, obs) = BindToDirect(source, view, viewExpression); + if (converterObj is not null) + { + // Use the converter + var source = + Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression) + .SelectMany(x => + !BindingTypeConverterDispatch.TryConvertAny( + converterObj, + x?.GetType() ?? typeof(object), + x, + viewType, + conversionHint, + out var tmp) + ? Observable.Empty + : Observable.Return(tmp)); + + var (disposable, obs) = BindToDirect(source, view, viewExpression); + return new ReactiveBinding(view, viewExpression, vmExpression, obs, BindingDirection.OneWay, disposable); + } - return new ReactiveBinding(view, viewExpression, vmExpression, obs, BindingDirection.OneWay, disposable); + // No converter found - check if types are directly assignable + if (viewType.IsAssignableFrom(typeof(TVMProp))) + { + // No conversion needed - direct assignment + var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Select(x => (object?)x); + var (disposable, obs) = BindToDirect(source, view, viewExpression); + return new ReactiveBinding(view, viewExpression, vmExpression, obs, BindingDirection.OneWay, disposable); + } + + // No converter and types not assignable - throw exception + throw new ArgumentException($"Can't convert {typeof(TVMProp)} to {viewType}. To fix this, register a IBindingTypeConverter"); } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] -#endif public IReactiveBinding OneWayBind( TViewModel? viewModel, TView view, @@ -177,6 +236,7 @@ public IReactiveBinding OneWayBind( var vmExpression = Reflection.Rewrite(vmProperty.Body); var viewExpression = Reflection.Rewrite(viewProperty.Body); + var ret = EvalBindingHooks(viewModel, view, vmExpression, viewExpression, BindingDirection.OneWay); if (!ret) { @@ -191,10 +251,6 @@ public IReactiveBinding OneWayBind( } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] -#endif public IDisposable BindTo( IObservable observedChange, TTarget? target, @@ -214,21 +270,233 @@ public IDisposable BindTo( return Disposable.Empty; } - var converter = (vmToViewConverterOverride ?? GetConverterForTypes(typeof(TValue), typeof(TTValue?))) ?? throw new ArgumentException($"Can't convert {typeof(TValue)} to {typeof(TTValue)}. To fix this, register a IBindingTypeConverter"); - var source = observedChange.SelectMany(x => !converter.TryConvert(x, typeof(TTValue?), conversionHint, out var tmp) ? Observable.Empty : Observable.Return(tmp)); + // First, try to find a registered converter (prioritize user-registered converters) + var converterObj = vmToViewConverterOverride ?? GetConverterForTypes(typeof(TValue), typeof(TTValue?)); + + if (converterObj is not null) + { + // Use the converter + var source = + observedChange.SelectMany(x => + !BindingTypeConverterDispatch.TryConvertAny( + converterObj, + typeof(TValue), + x, + typeof(TTValue?), + conversionHint, + out var tmp) + ? Observable.Empty + : Observable.Return(tmp)); + + var (disposable, _) = BindToDirect(source, target!, viewExpression); + return disposable; + } + + // No converter found - check if types are directly assignable (includes same type and compatible reference types) + if (typeof(TTValue).IsAssignableFrom(typeof(TValue))) + { + // No conversion needed - direct assignment + var source = observedChange.Select(x => (object?)x); + var (disposable, _) = BindToDirect(source, target!, viewExpression); + return disposable; + } + + // No converter and types not assignable - throw exception + throw new ArgumentException($"Can't convert {typeof(TValue)} to {typeof(TTValue)}. To fix this, register a IBindingTypeConverter"); + } + + /// + /// Gets a converter for the specified type pair. + /// + /// The source type. + /// The target type. + /// + /// A converter instance (either or ) + /// if one is registered for the specified types; otherwise, . + /// + /// + /// + /// Typed converters (exact pair match) are preferred over fallback converters. + /// + /// + /// This method uses which provides lock-free converter lookup + /// with built-in affinity-based selection. No external caching is needed. + /// + /// + internal static object? GetConverterForTypes(Type lhs, Type rhs) => + ResolveBestConverter(lhs, rhs); + + /// + /// Resolves the best converter for a given type pair using the ConverterService. + /// + /// The source type. + /// The target type. + /// + /// The selected converter (typed preferred), or if none matches. + /// + /// + /// + /// This method first attempts to use for lock-free converter resolution. + /// If no ConverterService is available (legacy initialization), it falls back to Splat-based resolution. + /// + /// + /// The ConverterService provides: + /// + /// Lock-free reads via snapshot pattern + /// Built-in affinity-based selection (highest wins) + /// Two-phase resolution: typed converters first, then fallback converters + /// + /// + /// + private static object? ResolveBestConverter(Type fromType, Type toType) + { + // Try to use the new ConverterService first (lock-free, optimized) + try + { + var converter = RxConverters.Current.ResolveConverter(fromType, toType); + if (converter is not null) + { + return converter; + } + } + catch + { + // ConverterService not available, fall back to Splat + } + + // Fallback to Splat-based resolution for backward compatibility + // Phase 1: exact-pair typed converters by affinity. + var typed = AppLocator.Current.GetServices(); + var bestTypedScore = -1; + IBindingTypeConverter? bestTyped = null; + + foreach (var c in typed) + { + if (c is null || c.FromType != fromType || c.ToType != toType) + { + continue; + } + + var score = c.GetAffinityForObjects(); + if (score > bestTypedScore && score > 0) + { + bestTypedScore = score; + bestTyped = c; + } + } + + if (bestTyped is not null) + { + return bestTyped; + } + + // Phase 2: fallback converters by affinity. + var fallbacks = AppLocator.Current.GetServices(); + var bestFallbackScore = -1; + IBindingFallbackConverter? bestFallback = null; + + foreach (var c in fallbacks) + { + if (c is null) + { + continue; + } + + var score = c.GetAffinityForObjects(fromType, toType); + if (score > bestFallbackScore && score > 0) + { + bestFallbackScore = score; + bestFallback = c; + } + } + + return bestFallback; + } - var (disposable, _) = BindToDirect(source, target!, viewExpression); + /// + /// Converts an expression chain to a materialized array using collection expression syntax. + /// + /// The expression whose chain should be materialized. + /// + /// An array of expressions representing the chain, or when the chain cannot be obtained. + /// + private static Expression[]? GetExpressionChainArrayOrNull(Expression? expression) => + expression is null ? null : [.. expression.GetExpressionChain()]; + + /// + /// Creates the default value instance for used by the "replay on host changes" logic. + /// + /// The member type. + /// + /// A boxed default value for value types, or for reference types. + /// + private static object? CreateDefaultValueForType(Type type) + { + ArgumentExceptionHelper.ThrowIfNull(type); - return disposable; + return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null; } - internal static IBindingTypeConverter? GetConverterForTypes(Type lhs, Type rhs) => - _typeConverterCache.Get((lhs, rhs)); + /// + /// Determines whether values should be replayed when the host changes. + /// + /// The host expression chain. + /// + /// when replay-on-host-change behavior should be enabled; otherwise . + /// + /// + /// This preserves the original behavior: when the chain includes IViewFor.ViewModel, replay is disabled. + /// + private static bool ShouldReplayOnHostChanges(Expression[]? hostExpressionChain) + { + if (hostExpressionChain is null) + { + return true; + } + + for (var i = 0; i < hostExpressionChain.Length; i++) + { + if (hostExpressionChain[i] is MemberExpression member && + string.Equals(member.Member.Name, nameof(IViewFor.ViewModel), StringComparison.Ordinal)) + { + return false; + } + } + + return true; + } + + /// + /// Resolves the current host object for the binding by evaluating the host expression chain. + /// + /// The root binding target. + /// The expression chain used to compute the host. + /// + /// The resolved host object, or if the chain cannot be evaluated. + /// + private static object? ResolveHostFromChainOrNull(object target, Expression[] hostExpressionChain) + { + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(hostExpressionChain); + + object? host = target; + + if (!Reflection.TryGetValueForPropertyChain(out host, host, hostExpressionChain)) + { + return null; + } + + return host; + } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Type conversion requires dynamic code generation")] - [RequiresUnreferencedCode("Type conversion may reference types that could be trimmed")] -#endif + /// + /// Gets the set-method conversion shim for an assignment into a target member. + /// + /// The runtime type of the inbound value. + /// The target member type. + /// + /// A conversion function used by set-method converters, or if no converter is applicable. + /// private static Func? GetSetConverter(Type? fromType, Type? targetType) { if (fromType is null) @@ -236,181 +504,332 @@ public IDisposable BindTo( return null; } - var setter = _setMethodCache.Get((fromType, targetType)); - return setter is null ? null : setter.PerformSet; + var converter = _setMethodCache.GetOrAdd( + (fromType, targetType), + static key => ResolveBestSetMethodConverter(key.fromType, key.toType)); + + if (converter is null) + { + return null; + } + + // Adapt the converter's contract to the local call shape expected by SetThenGet. + // Note: do not store this delegate in the cache; the cache stores the converter instance. + return (currentValue, newValue, indexParameters) => converter.PerformSet(currentValue, newValue, indexParameters); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Property binding requires dynamic code generation")] - [RequiresUnreferencedCode("Property binding may reference members that could be trimmed")] -#endif - private (IDisposable disposable, IObservable value) BindToDirect( - IObservable changeObservable, - TTarget target, - Expression viewExpression) - where TTarget : class + /// + /// Resolves the best for a given pair. + /// + /// The inbound runtime type. + /// The target type. + /// The selected converter, or if none matches. + private static ISetMethodBindingConverter? ResolveBestSetMethodConverter(Type fromType, Type? toType) { - var memberInfo = viewExpression.GetMemberInfo(); + var converters = AppLocator.Current.GetServices(); - var defaultSetter = Reflection.GetValueSetterOrThrow(memberInfo); - var defaultGetter = Reflection.GetValueFetcherOrThrow(memberInfo); - var synchronizedChanges = changeObservable.Synchronize(); + var bestScore = -1; + ISetMethodBindingConverter? best = null; - (bool shouldEmit, object? value) SetThenGet(object? paramTarget, object? paramValue, object?[]? paramParams) + foreach (var c in converters) { - var converter = GetSetConverter(paramValue?.GetType(), viewExpression.Type); + if (c is null) + { + continue; + } - if (defaultGetter is null) + var score = c.GetAffinityForObjects(fromType, toType); + if (score > bestScore && score > 0) { - throw new InvalidOperationException($"{nameof(defaultGetter)} was not found."); + bestScore = score; + best = c; } + } + + return best; + } + + /// + /// Determines whether is a direct member access on the root parameter. + /// + /// The view expression to inspect. + /// + /// if the member is directly on the root parameter; otherwise . + /// + private static bool IsDirectMemberOnRootParameter(Expression viewExpression) => + viewExpression.GetParent()?.NodeType == ExpressionType.Parameter; + + /// + /// Creates an observable that applies changes directly to when the member is directly on the root parameter. + /// + /// The target object type. + /// The value type emitted by the returned observable. + /// The change element type. + /// The synchronized change stream. + /// The target object. + /// The view expression describing the member to set. + /// The set-then-get delegate. + /// An observable sequence of values that were effectively set. + private static IObservable CreateDirectSetObservable( + IObservable synchronizedChanges, + TTarget target, + Expression viewExpression, + Func setThenGet) + where TTarget : class + { + ArgumentExceptionHelper.ThrowIfNull(synchronizedChanges); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(viewExpression); + ArgumentExceptionHelper.ThrowIfNull(setThenGet); + + var arguments = viewExpression.GetArgumentsArray(); + + return synchronizedChanges.Select(value => setThenGet(target, value, arguments)) + .Where(result => result.shouldEmit) + .Select(result => result.value is null ? default! : (TValue)result.value); + } + + /// + /// Creates the core "set then get" function that applies a value to a target member and returns whether a value should be emitted. + /// + /// The view expression describing the member to set. + /// The compiled getter for the member. + /// The compiled setter for the member. + /// + /// A delegate that sets and then gets the value, returning whether the value should be emitted and the resulting value. + /// + private static Func CreateSetThenGet( + Expression viewExpression, + Func getter, + Action setter) + { + ArgumentExceptionHelper.ThrowIfNull(viewExpression); + ArgumentExceptionHelper.ThrowIfNull(getter); + ArgumentExceptionHelper.ThrowIfNull(setter); + + return (paramTarget, paramValue, paramParams) => + { + var converter = GetSetConverter(paramValue?.GetType(), viewExpression.Type); if (converter is null) { - var currentValue = defaultGetter(paramTarget, paramParams); - var shouldUpdate = !EqualityComparer.Default.Equals(currentValue, paramValue); - - if (!shouldUpdate) + var currentValue = getter(paramTarget, paramParams); + if (EqualityComparer.Default.Equals(currentValue, paramValue)) { return (false, currentValue); } - defaultSetter?.Invoke(paramTarget, paramValue, paramParams); - return (true, defaultGetter(paramTarget, paramParams)); + setter(paramTarget, paramValue, paramParams); + return (true, getter(paramTarget, paramParams)); } - var value = defaultGetter(paramTarget, paramParams); - var convertedValue = converter(value, paramValue, paramParams); - var shouldEmit = !EqualityComparer.Default.Equals(value, convertedValue); + var existing = getter(paramTarget, paramParams); + var converted = converter(existing, paramValue, paramParams); + var shouldEmit = !EqualityComparer.Default.Equals(existing, converted); + return (shouldEmit, converted); + }; + } - return (shouldEmit, convertedValue); - } + /// + /// Creates an observable that applies changes to a member whose host is obtained via a property chain. + /// + /// The root target object type. + /// The value type emitted by the returned observable. + /// The change element type. + /// The synchronized change stream. + /// The root target object. + /// The view expression describing the member to set. + /// The set-then-get delegate. + /// The compiled getter for the member. + /// An observable sequence of values that were effectively set. + /// Thrown when the host expression cannot be obtained. + private static IObservable CreateChainedSetObservable( + IObservable synchronizedChanges, + TTarget target, + Expression viewExpression, + Func setThenGet, + Func getter) + where TTarget : class + { + ArgumentExceptionHelper.ThrowIfNull(synchronizedChanges); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(viewExpression); + ArgumentExceptionHelper.ThrowIfNull(setThenGet); + ArgumentExceptionHelper.ThrowIfNull(getter); - IObservable setObservable; + var hostExpression = viewExpression.GetParent() ?? throw new InvalidOperationException("Host expression was not found."); + var hostChain = GetExpressionChainArrayOrNull(hostExpression); + var hostChanges = target.WhenAnyDynamic(hostExpression, x => x.Value).Synchronize(); + var arguments = viewExpression.GetArgumentsArray(); + var propertyDefaultValue = CreateDefaultValueForType(viewExpression.Type); - if (viewExpression.GetParent()?.NodeType == ExpressionType.Parameter) - { - var arguments = viewExpression.GetArgumentsArray(); - setObservable = synchronizedChanges - .Select(value => SetThenGet(target, value, arguments)) - .Where(result => result.shouldEmit) - .Select(result => result.value is null ? default! : (TValue)result.value); - } - else + var shouldReplayOnHostChanges = ShouldReplayOnHostChanges(hostChain); + + return Observable.Create(observer => { - var hostExpression = viewExpression.GetParent(); - var hostExpressionChain = hostExpression?.GetExpressionChain()?.ToArray(); - var hostChanges = target.WhenAnyDynamic(hostExpression, x => x.Value).Synchronize(); - var arguments = viewExpression.GetArgumentsArray(); - var propertyDefaultValue = viewExpression.Type.GetTypeInfo().IsValueType ? Activator.CreateInstance(viewExpression.Type) : null; - var shouldReplayOnHostChanges = hostExpressionChain? - .OfType() - .Any(static expression => string.Equals(expression.Member.Name, nameof(IViewFor.ViewModel), StringComparison.Ordinal)) != true; + ArgumentExceptionHelper.ThrowIfNull(observer); + + object? latestHost = null; + object? currentHost = null; + object? lastObservedValue = null; + var hasObservedValue = false; - setObservable = Observable.Create(observer => + bool HostPropertyEqualsDefault(object? host) { - object? latestHost = null; - object? lastObservedValue = null; - object? currentHost = null; - var hasObservedValue = false; + if (host is null) + { + return false; + } + + var currentValue = getter(host, arguments); + return EqualityComparer.Default.Equals(currentValue, propertyDefaultValue); + } - bool HostPropertyEqualsDefault(object? host) + void ApplyValueToHost(object? host, object? value) + { + if (host is null || !hasObservedValue) { - if (host is null || defaultGetter is null) - { - return false; - } + return; + } - var currentValue = defaultGetter(host, arguments); - return EqualityComparer.Default.Equals(currentValue, propertyDefaultValue); + var (shouldEmit, result) = setThenGet(host, value, arguments); + if (!shouldEmit) + { + return; } - void ApplyValueToHost(object? host, object? value) + observer.OnNext(result is null ? default! : (TValue)result); + } + + var hostDisposable = hostChanges.Subscribe( + hostValue => { - if (host is null || !hasObservedValue) + latestHost = hostValue; + + if (ReferenceEquals(hostValue, currentHost)) { return; } - var (shouldEmit, result) = SetThenGet(host, value, arguments); - if (!shouldEmit) + currentHost = hostValue; + + if (!shouldReplayOnHostChanges || !hasObservedValue || !HostPropertyEqualsDefault(hostValue)) { return; } - observer.OnNext(result is null ? default! : (TValue)result); - } + ApplyValueToHost(hostValue, lastObservedValue); + }, + observer.OnError); - var hostDisposable = hostChanges - .Subscribe( - hostValue => - { - latestHost = hostValue; - - if (ReferenceEquals(hostValue, currentHost)) - { - return; - } - - currentHost = hostValue; - - if (!shouldReplayOnHostChanges || !hasObservedValue || !HostPropertyEqualsDefault(hostValue)) - { - return; - } - - ApplyValueToHost(hostValue, lastObservedValue); - }, - observer.OnError); - - var changeDisposable = synchronizedChanges - .Subscribe( - value => - { - hasObservedValue = true; - lastObservedValue = value; - - var host = latestHost; - if (hostExpressionChain is not null) - { - if (!Reflection.TryGetValueForPropertyChain(out host, target, hostExpressionChain)) - { - host = null; - } - - latestHost = host; - } - - if (host is null) - { - return; - } - - ApplyValueToHost(host, value); - }, - observer.OnError); - - return new CompositeDisposable(hostDisposable, changeDisposable); - }); - } + var changeDisposable = synchronizedChanges.Subscribe( + value => + { + hasObservedValue = true; + lastObservedValue = value; - return (setObservable.Subscribe(_ => { }, ex => - { - this.Log().Error(ex, $"{viewExpression} Binding received an Exception!"); - if (ex.InnerException is null) + var host = latestHost; + + if (hostChain is not null) + { + host = ResolveHostFromChainOrNull(target, hostChain); + latestHost = host; + } + + if (host is null) + { + return; + } + + ApplyValueToHost(host, value); + }, + observer.OnError); + + return new CompositeDisposable(hostDisposable, changeDisposable); + }); + } + + /// + /// Binds an observable to a target member directly using compiled accessors. + /// + /// The target object type. + /// The value type emitted by the returned observable. + /// The element type produced by . + /// The observable providing values to set. + /// The target object. + /// The rewritten member expression describing the target member. + /// + /// A tuple containing the subscription and an observable sequence of values that were effectively set. + /// + /// Thrown when a required getter cannot be resolved. + private (IDisposable disposable, IObservable value) BindToDirect( + IObservable changeObservable, + TTarget target, + Expression viewExpression) + where TTarget : class + { + ArgumentExceptionHelper.ThrowIfNull(changeObservable); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(viewExpression); + + var memberInfo = viewExpression.GetMemberInfo(); + + var setter = Reflection.GetValueSetterOrThrow(memberInfo); + var getter = Reflection.GetValueFetcherOrThrow(memberInfo) ?? throw new InvalidOperationException("getter was not found."); + + var synchronizedChanges = changeObservable.Synchronize(); + var setThenGet = CreateSetThenGet(viewExpression, getter, setter); + + IObservable setObservable = + IsDirectMemberOnRootParameter(viewExpression) + ? CreateDirectSetObservable(synchronizedChanges, target, viewExpression, setThenGet) + : CreateChainedSetObservable(synchronizedChanges, target, viewExpression, setThenGet, getter); + + var subscription = SubscribeWithBindingErrorHandling(setObservable, viewExpression); + + return (subscription, setObservable); + } + + /// + /// Subscribes to and applies binding error handling consistent with the binding engine. + /// + /// The element type of the observable. + /// The observable to subscribe to. + /// The view expression used for diagnostic messages. + /// The subscription disposable. + /// + /// Thrown when the binding receives an exception with an inner exception, matching legacy behavior. + /// + private IDisposable SubscribeWithBindingErrorHandling(IObservable setObservable, Expression viewExpression) + { + ArgumentExceptionHelper.ThrowIfNull(setObservable); + ArgumentExceptionHelper.ThrowIfNull(viewExpression); + + return setObservable.Subscribe( + _ => { }, + ex => { - return; - } + this.Log().Error(ex, $"{viewExpression} Binding received an Exception!"); + if (ex.InnerException is null) + { + return; + } - // If the exception is not null, we throw it wrapped in a TargetInvocationException. - throw new TargetInvocationException($"{viewExpression} Binding received an Exception!", ex.InnerException); - }), setObservable); + throw new TargetInvocationException($"{viewExpression} Binding received an Exception!", ex.InnerException); + }); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Property binding requires dynamic code generation")] - [RequiresUnreferencedCode("Property binding may reference members that could be trimmed")] -#endif + /// + /// Evaluates registered instances to determine whether a binding should be created. + /// + /// The view model type. + /// The view type. + /// The view model instance (may be ). + /// The view instance. + /// The rewritten view model expression. + /// The rewritten view expression. + /// The binding direction. + /// if the binding should proceed; otherwise . private bool EvalBindingHooks(TViewModel? viewModel, TView view, Expression vmExpression, Expression viewExpression, BindingDirection direction) where TViewModel : class where TView : class, IViewFor @@ -418,25 +837,41 @@ private bool EvalBindingHooks(TViewModel? viewModel, TView vi var hooks = AppLocator.Current.GetServices(); ArgumentExceptionHelper.ThrowIfNull(view); + // Compile chains once for hook evaluation. + var vmChainGetter = vmExpression != null + ? new Reflection.CompiledPropertyChain([.. vmExpression.GetExpressionChain()]) + : null; + var viewChainGetter = new Reflection.CompiledPropertyChain([.. viewExpression.GetExpressionChain()]); + Func[]> vmFetcher = vmExpression is not null ? (() => - { - Reflection.TryGetAllValuesForPropertyChain(out var fetchedValues, viewModel, vmExpression.GetExpressionChain()); - return fetchedValues; - }) - : (() => - [ - new ObservedChange(null!, null, viewModel) - ]); + { + vmChainGetter!.TryGetAllValues(viewModel, out var fetchedValues); + return fetchedValues; + }) + : (() => [new ObservedChange(null!, null, viewModel)]); - var vFetcher = new Func[]>(() => + Func[]> vFetcher = () => { - Reflection.TryGetAllValuesForPropertyChain(out var fetchedValues, view, viewExpression.GetExpressionChain()); + viewChainGetter.TryGetAllValues(view, out var fetchedValues); return fetchedValues; - }); + }; - var shouldBind = hooks.Aggregate(true, (acc, x) => - acc && x.ExecuteHook(viewModel, view!, vmFetcher!, vFetcher!, direction)); + // Replace Aggregate with a loop to avoid enumerator overhead and closures. + var shouldBind = true; + foreach (var hook in hooks) + { + if (hook is null) + { + continue; + } + + if (!hook.ExecuteHook(viewModel, view!, vmFetcher!, vFetcher!, direction)) + { + shouldBind = false; + break; + } + } if (!shouldBind) { @@ -448,10 +883,6 @@ private bool EvalBindingHooks(TViewModel? viewModel, TView vi return shouldBind; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Property binding requires dynamic code generation")] - [RequiresUnreferencedCode("Property binding may reference members that could be trimmed")] -#endif private ReactiveBinding BindImpl( TViewModel? viewModel, TView view, @@ -465,34 +896,48 @@ private bool EvalBindingHooks(TViewModel? viewModel, TView vi where TView : class, IViewFor { ArgumentExceptionHelper.ThrowIfNull(vmProperty); - ArgumentExceptionHelper.ThrowIfNull(viewProperty); var signalInitialUpdate = new Subject(); var vmExpression = Reflection.Rewrite(vmProperty.Body); var viewExpression = Reflection.Rewrite(viewProperty.Body); + // Pre-compile expression chains ONCE at binding setup time. + // This is the "reflection boundary". + Expression[] vmExpressionChainArray = [.. vmExpression.GetExpressionChain()]; + Expression[] viewExpressionChainArray = [.. viewExpression.GetExpressionChain()]; + + // VM chain expects root = view.ViewModel (object?). + var vmChainGetter = new Reflection.CompiledPropertyChain(vmExpressionChainArray); + + // View chain expects root = view (TView). + var viewChainGetter = new Reflection.CompiledPropertyChain(viewExpressionChainArray); + + // Setters for two-way binding. + var viewChainSetter = new Reflection.CompiledPropertyChainSetter(viewExpressionChainArray); + var vmChainSetter = new Reflection.CompiledPropertyChainSetter(vmExpressionChainArray); + IObservable<(bool isValid, object? view, bool isViewModel)>? changeWithValues = null; if (triggerUpdate == TriggerUpdate.ViewToViewModel) { var signalObservable = signalViewUpdate is not null - ? signalViewUpdate.Select(_ => false) - : view.WhenAnyDynamic(viewExpression, x => (TVProp?)x.Value).Select(_ => false); + ? signalViewUpdate.Select(_ => false) + : view.WhenAnyDynamic(viewExpression, x => (TVProp?)x.Value).Select(_ => false); var somethingChanged = Observable.Merge( - Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Select(_ => true), - signalInitialUpdate.Select(_ => true), - signalObservable); + Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Select(_ => true), + signalInitialUpdate.Select(_ => true), + signalObservable); changeWithValues = somethingChanged.Select(isVm => - !Reflection.TryGetValueForPropertyChain(out TVMProp vmValue, view.ViewModel, vmExpression.GetExpressionChain()) || - !Reflection.TryGetValueForPropertyChain(out TVProp vValue, view, viewExpression.GetExpressionChain()) + !vmChainGetter.TryGetValue(view.ViewModel, out TVMProp vmValue) || + !viewChainGetter.TryGetValue(view, out TVProp vValue) ? (false, null, false) : isVm ? !vmToViewConverter(vmValue, out var vmAsView) || EqualityComparer.Default.Equals(vValue, vmAsView) - ? (false, null, false) - : (true, vmAsView, isVm) + ? (false, null, false) + : (true, vmAsView, isVm) : !viewToVmConverter(vValue, out var vAsViewModel) || EqualityComparer.Default.Equals(vmValue, vAsViewModel) ? (false, null, false) : (true, vAsViewModel, isVm)); @@ -500,25 +945,24 @@ private bool EvalBindingHooks(TViewModel? viewModel, TView vi else { var somethingChanged = Observable.Merge( - signalViewUpdate is null ? - Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Select(_ => true) : - signalViewUpdate.Select(_ => true) - .Merge(Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Select(_ => true).Take(1)), - signalInitialUpdate.Select(_ => true), - view.WhenAnyDynamic(viewExpression, x => (TVProp?)x.Value).Select(_ => false)); - - changeWithValues = somethingChanged - .Select(isVm => - !Reflection.TryGetValueForPropertyChain(out TVMProp vmValue, view.ViewModel, vmExpression.GetExpressionChain()) || - !Reflection.TryGetValueForPropertyChain(out TVProp vValue, view, viewExpression.GetExpressionChain()) - ? (false, null, false) - : isVm - ? !vmToViewConverter(vmValue, out var vmAsView) || EqualityComparer.Default.Equals(vValue, vmAsView) - ? (false, null, false) - : (true, vmAsView, isVm) - : !viewToVmConverter(vValue, out var vAsViewModel) || EqualityComparer.Default.Equals(vmValue, vAsViewModel) - ? (false, null, false) - : (true, vAsViewModel, isVm)); + signalViewUpdate is null + ? Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Select(_ => true) + : signalViewUpdate.Select(_ => true) + .Merge(Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Select(_ => true).Take(1)), + signalInitialUpdate.Select(_ => true), + view.WhenAnyDynamic(viewExpression, x => (TVProp?)x.Value).Select(_ => false)); + + changeWithValues = somethingChanged.Select(isVm => + !vmChainGetter.TryGetValue(view.ViewModel, out TVMProp vmValue) || + !viewChainGetter.TryGetValue(view, out TVProp vValue) + ? (false, null, false) + : isVm + ? !vmToViewConverter(vmValue, out var vmAsView) || EqualityComparer.Default.Equals(vValue, vmAsView) + ? (false, null, false) + : (true, vmAsView, isVm) + : !viewToVmConverter(vValue, out var vAsViewModel) || EqualityComparer.Default.Equals(vmValue, vAsViewModel) + ? (false, null, false) + : (true, vAsViewModel, isVm)); } var ret = EvalBindingHooks(viewModel, view, vmExpression, viewExpression, BindingDirection.TwoWay); @@ -527,34 +971,34 @@ signalViewUpdate is null ? return null!; } - var changes = changeWithValues - .Where(value => value.isValid) - .Select(value => (value.view, value.isViewModel)) - .Publish() - .RefCount(); + var changes = + changeWithValues + .Where(value => value.isValid) + .Select(value => (value.view, value.isViewModel)) + .Publish() + .RefCount(); var disposable = changes.Subscribe(isVmWithLatestValue => { if (isVmWithLatestValue.isViewModel) { - Reflection.TrySetValueToPropertyChain(view, viewExpression.GetExpressionChain(), isVmWithLatestValue.view, false); + viewChainSetter.TrySetValue(view, isVmWithLatestValue.view, false); } else { - Reflection.TrySetValueToPropertyChain(view.ViewModel, vmExpression.GetExpressionChain(), isVmWithLatestValue.view, false); + vmChainSetter.TrySetValue(view.ViewModel, isVmWithLatestValue.view, false); } }); - // NB: Even though it's technically a two-way bind, most people - // want the ViewModel to win at first. + // NB: Even though it's technically a two-way bind, most people want the ViewModel to win at first. signalInitialUpdate.OnNext(true); return new ReactiveBinding( - view, - viewExpression, - vmExpression, - changes, - BindingDirection.TwoWay, - disposable); + view, + viewExpression, + vmExpression, + changes, + BindingDirection.TwoWay, + disposable); } } diff --git a/src/ReactiveUI/Bindings/Property/PropertyBindingMixins.cs b/src/ReactiveUI/Bindings/Property/PropertyBindingMixins.cs index b12207ec70..58f2d5d498 100644 --- a/src/ReactiveUI/Bindings/Property/PropertyBindingMixins.cs +++ b/src/ReactiveUI/Bindings/Property/PropertyBindingMixins.cs @@ -30,13 +30,14 @@ namespace ReactiveUI; /// ]]> /// /// +[RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] +[RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflection-based invocation.")] public static class PropertyBindingMixins { private static readonly PropertyBinderImplementation _binderImplementation; static PropertyBindingMixins() { - RxApp.EnsureInitialized(); _binderImplementation = new PropertyBinderImplementation(); } @@ -75,10 +76,6 @@ static PropertyBindingMixins() /// An instance of that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which require dynamic code generation")] - [RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may require unreferenced code")] -#endif public static IReactiveBinding Bind( this TView view, TViewModel? viewModel, @@ -131,10 +128,6 @@ static PropertyBindingMixins() /// An instance of that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which require dynamic code generation")] - [RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may require unreferenced code")] -#endif public static IReactiveBinding Bind( this TView view, TViewModel? viewModel, @@ -180,10 +173,6 @@ static PropertyBindingMixins() /// An instance of that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which require dynamic code generation")] - [RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may require unreferenced code")] -#endif public static IReactiveBinding Bind( this TView view, TViewModel? viewModel, @@ -223,10 +212,6 @@ static PropertyBindingMixins() /// An instance of that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which require dynamic code generation")] - [RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may require unreferenced code")] -#endif public static IReactiveBinding Bind( this TView view, TViewModel? viewModel, @@ -276,10 +261,6 @@ static PropertyBindingMixins() /// An instance of that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which require dynamic code generation")] - [RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may require unreferenced code")] -#endif public static IReactiveBinding OneWayBind( this TView view, TViewModel? viewModel, @@ -327,10 +308,6 @@ public static IReactiveBinding OneWayBind that, when disposed, /// disconnects the binding. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which require dynamic code generation")] - [RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may require unreferenced code")] -#endif public static IReactiveBinding OneWayBind( this TView view, TViewModel? viewModel, @@ -365,10 +342,6 @@ public static IReactiveBinding OneWayBind /// An object that when disposed, disconnects the binding. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which require dynamic code generation")] - [RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may require unreferenced code")] -#endif public static IDisposable BindTo( this IObservable @this, TTarget? target, diff --git a/src/ReactiveUI/Builder/IReactiveUIBuilder.cs b/src/ReactiveUI/Builder/IReactiveUIBuilder.cs index 442755d485..de16ecf61f 100644 --- a/src/ReactiveUI/Builder/IReactiveUIBuilder.cs +++ b/src/ReactiveUI/Builder/IReactiveUIBuilder.cs @@ -29,7 +29,7 @@ namespace ReactiveUI.Builder; /// .RegisterSingletonViewModel() /// .WithRegistration(resolver => /// { -/// resolver.RegisterLazySingleton(() => new ApiClient(), typeof(IApiClient)); +/// resolver.RegisterLazySingleton(() => new ApiClient()); /// }) /// .BuildApp()); /// ]]> @@ -45,6 +45,13 @@ public interface IReactiveUIBuilder : IAppBuilder /// The builder instance for chaining. IReactiveUIBuilder ConfigureMessageBus(Action configure); + /// + /// Registers a custom message bus instance. + /// + /// The message bus instance to use. + /// The builder instance for chaining. + IReactiveUIBuilder WithMessageBus(IMessageBus messageBus); + /// /// Configures the suspension driver. /// @@ -129,10 +136,6 @@ IReactiveUIBuilder RegisterViewModel() /// /// The type of the registration module that implements IWantsToRegisterStuff. /// The builder instance for chaining. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif IReactiveUIBuilder WithPlatformModule() where T : IWantsToRegisterStuff, new(); @@ -140,10 +143,6 @@ IReactiveUIBuilder WithPlatformModule() /// Withes the platform services. /// /// The builder instance for chaining. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("ProcessRegistrationForNamespace uses reflection to locate types which may be trimmed.")] - [RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] -#endif IReactiveUIBuilder WithPlatformServices(); /// @@ -170,16 +169,44 @@ IReactiveUIBuilder WithPlatformModule() /// IReactiveUIBuilder WithTaskPoolScheduler(IScheduler scheduler, bool setRxApp = true); + /// + /// Configures a custom exception handler for unhandled errors in ReactiveUI observables. + /// If not configured, ReactiveUI uses a default handler that breaks the debugger and throws UnhandledErrorException. + /// + /// The custom exception handler to use. + /// The builder instance for chaining. + IReactiveUIBuilder WithExceptionHandler(IObserver exceptionHandler); + + /// + /// Configures the non-generic suspension host for application lifecycle management. + /// Creates a default instance if not explicitly provided. + /// + /// The builder instance for chaining. + IReactiveUIBuilder WithSuspensionHost(); + + /// + /// Configures a typed suspension host for application lifecycle management. + /// Creates a instance configured for the specified app state type. + /// + /// The type of the application state to manage. + /// The builder instance for chaining. + IReactiveUIBuilder WithSuspensionHost(); + + /// + /// Configures custom cache size limits for ReactiveUI's internal memoizing caches. + /// If not configured, platform-specific defaults are used (32/64 for mobile, 64/256 for desktop). + /// + /// The small cache limit to use (must be greater than 0). + /// The big cache limit to use (must be greater than 0). + /// The builder instance for chaining. + IReactiveUIBuilder WithCacheSizes(int smallCacheLimit, int bigCacheLimit); + /// /// Withes the views from assembly. /// /// The assembly. /// The builder instance for chaining. - -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif + [RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibility, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] IReactiveUIBuilder WithViewsFromAssembly(Assembly assembly); /// diff --git a/src/ReactiveUI/Builder/ReactiveUIBuilder.cs b/src/ReactiveUI/Builder/ReactiveUIBuilder.cs index 371a373015..d0d94fe2d1 100644 --- a/src/ReactiveUI/Builder/ReactiveUIBuilder.cs +++ b/src/ReactiveUI/Builder/ReactiveUIBuilder.cs @@ -19,6 +19,11 @@ public sealed class ReactiveUIBuilder : AppBuilder, IReactiveUIBuilder, IReactiv private bool _coreRegistered; private bool _setRxAppMainScheduler; private bool _setRxAppTaskPoolScheduler; + private IObserver? _exceptionHandler; + private ISuspensionHost? _suspensionHost; + private int? _smallCacheLimit; + private int? _bigCacheLimit; + private IMessageBus? _messageBus; /// /// Initializes a new instance of the class. @@ -27,7 +32,13 @@ public sealed class ReactiveUIBuilder : AppBuilder, IReactiveUIBuilder, IReactiv /// The configured services. /// resolver. public ReactiveUIBuilder(IMutableDependencyResolver resolver, IReadonlyDependencyResolver? current) - : base(resolver, current) => CurrentMutable.InitializeSplat(); + : base(resolver, current) + { + CurrentMutable.InitializeSplat(); + + // Register the ConverterService instance so it's accessible to registrations + CurrentMutable.RegisterConstant(() => ConverterService); + } /// /// Gets a scheduler used to schedule work items that @@ -44,6 +55,20 @@ public ReactiveUIBuilder(IMutableDependencyResolver resolver, IReadonlyDependenc /// public IScheduler? TaskpoolScheduler { get; private set; } + /// + /// Gets the converter service used for binding type conversions. + /// + /// + /// This service provides access to three specialized registries: + /// + /// - For exact type-pair converters + /// - For fallback converters with runtime type checking + /// - For set-method converters + /// + /// Use the WithConverter* methods to register converters during application initialization. + /// + public ConverterService ConverterService { get; } = new(); + /// /// Configures the main thread scheduler for ReactiveUI. /// @@ -101,16 +126,27 @@ public IReactiveUIBuilder WithRegistration(Action co return this; } + /// + /// Registers services using an IWantsToRegisterStuff implementation. + /// This method provides a migration path for users with existing IWantsToRegisterStuff implementations. + /// + /// The registration implementation. + /// The builder instance for chaining. + /// Thrown if registration is null. + public IReactiveUIBuilder WithRegistration(IWantsToRegisterStuff registration) + { + ArgumentExceptionHelper.ThrowIfNull(registration); + + var registrar = new DependencyResolverRegistrar(CurrentMutable); + registration.Register(registrar); + + return this; + } + /// /// Registers the platform-specific ReactiveUI services. /// /// The builder instance for method chaining. -#if NET6_0_OR_GREATER - [SuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Not using reflection")] - [SuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Not using reflection")] - [RequiresUnreferencedCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] -#endif public IReactiveUIBuilder WithPlatformServices() { if (_platformRegistered) @@ -129,17 +165,14 @@ public IReactiveUIBuilder WithPlatformServices() /// Registers the core ReactiveUI services in an AOT-compatible manner. /// /// The builder instance for chaining. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [RequiresUnreferencedCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "In Splat")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "In Splat")] -#endif public override IAppBuilder WithCoreServices() { if (!_coreRegistered) { - // Immediately register the core ReactiveUI services into the provided resolver. + // Register all standard converters to the ConverterService + RegisterStandardConverters(); + + // Immediately register the core ReactiveUI services into the provided resolver (Splat). WithPlatformModule(); _coreRegistered = true; } @@ -155,10 +188,7 @@ public override IAppBuilder WithCoreServices() /// /// The assembly to scan for views. /// The builder instance for method chaining. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif + [RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibility, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] public IReactiveUIBuilder WithViewsFromAssembly(Assembly assembly) { ArgumentExceptionHelper.ThrowIfNull(assembly); @@ -173,15 +203,12 @@ public IReactiveUIBuilder WithViewsFromAssembly(Assembly assembly) /// /// The type of the registration module that implements IWantsToRegisterStuff. /// The builder instance for method chaining. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public IReactiveUIBuilder WithPlatformModule() where T : IWantsToRegisterStuff, new() { var registration = new T(); - registration.Register((f, t) => CurrentMutable.RegisterConstant(f(), t)); + var registrar = new DependencyResolverRegistrar(CurrentMutable); + registration.Register(registrar); return this; } @@ -259,6 +286,19 @@ public IReactiveUIBuilder ConfigureMessageBus(Action configure) => return messageBus; })); + /// + /// Registers a custom message bus instance. + /// + /// The message bus instance to use. + /// The builder instance for chaining. + public IReactiveUIBuilder WithMessageBus(IMessageBus messageBus) + { + ArgumentExceptionHelper.ThrowIfNull(messageBus); + + _messageBus = messageBus; + return WithRegistrationOnBuild(resolver => resolver.RegisterConstant(messageBus)); + } + /// /// Configures the ReactiveUI view locator. /// @@ -288,6 +328,301 @@ public IReactiveUIBuilder ConfigureSuspensionDriver(Action co } }); + /// + /// Registers a typed binding converter using the concrete type. + /// + /// The source type for the conversion. + /// The target type for the conversion. + /// The converter instance to register. + /// The builder instance for chaining. + /// Thrown if converter is null. + /// + /// + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithConverter(new MyCustomConverter<int, string>()) + /// .BuildApp(); + /// + /// + public IReactiveUIBuilder WithConverter(BindingTypeConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(converter); + ConverterService.TypedConverters.Register(converter); + return this; + } + + /// + /// Registers a typed binding converter using the interface. + /// + /// The converter instance to register. + /// The builder instance for chaining. + /// Thrown if converter is null. + /// + /// + /// IBindingTypeConverter converter = new MyCustomConverter(); + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithConverter(converter) + /// .BuildApp(); + /// + /// + public IReactiveUIBuilder WithConverter(IBindingTypeConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(converter); + ConverterService.TypedConverters.Register(converter); + return this; + } + + /// + /// Registers a typed binding converter via factory (lazy instantiation). + /// + /// The source type for the conversion. + /// The target type for the conversion. + /// The factory function that creates the converter. + /// The builder instance for chaining. + /// Thrown if factory is null. + /// + /// + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithConverter(() => new MyCustomConverter<int, string>()) + /// .BuildApp(); + /// + /// + public IReactiveUIBuilder WithConverter(Func> factory) + { + ArgumentExceptionHelper.ThrowIfNull(factory); + ConverterService.TypedConverters.Register(factory()); + return this; + } + + /// + /// Registers a typed binding converter via factory (interface, lazy instantiation). + /// + /// The factory function that creates the converter. + /// The builder instance for chaining. + /// Thrown if factory is null. + /// + /// + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithConverter(() => (IBindingTypeConverter)new MyCustomConverter()) + /// .BuildApp(); + /// + /// + public IReactiveUIBuilder WithConverter(Func factory) + { + ArgumentExceptionHelper.ThrowIfNull(factory); + ConverterService.TypedConverters.Register(factory()); + return this; + } + + /// + /// Registers a fallback binding converter. + /// + /// The fallback converter instance to register. + /// The builder instance for chaining. + /// Thrown if converter is null. + /// + /// Fallback converters are used when no exact type-pair converter is found. + /// They perform runtime type checking via . + /// + /// + /// + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithFallbackConverter(new MyFallbackConverter()) + /// .BuildApp(); + /// + /// + public IReactiveUIBuilder WithFallbackConverter(IBindingFallbackConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(converter); + ConverterService.FallbackConverters.Register(converter); + return this; + } + + /// + /// Registers a fallback binding converter via factory (lazy instantiation). + /// + /// The factory function that creates the fallback converter. + /// The builder instance for chaining. + /// Thrown if factory is null. + /// + /// + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithFallbackConverter(() => new MyFallbackConverter()) + /// .BuildApp(); + /// + /// + public IReactiveUIBuilder WithFallbackConverter(Func factory) + { + ArgumentExceptionHelper.ThrowIfNull(factory); + ConverterService.FallbackConverters.Register(factory()); + return this; + } + + /// + /// Registers a set-method binding converter. + /// + /// The set-method converter instance to register. + /// The builder instance for chaining. + /// Thrown if converter is null. + /// + /// Set-method converters are used for special binding scenarios where the target + /// uses a method (e.g., TableLayoutPanel.SetColumn) instead of a property setter. + /// + /// + /// + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithSetMethodConverter(new MySetMethodConverter()) + /// .BuildApp(); + /// + /// + public IReactiveUIBuilder WithSetMethodConverter(ISetMethodBindingConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(converter); + ConverterService.SetMethodConverters.Register(converter); + return this; + } + + /// + /// Registers a set-method binding converter via factory (lazy instantiation). + /// + /// The factory function that creates the set-method converter. + /// The builder instance for chaining. + /// Thrown if factory is null. + /// + /// + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithSetMethodConverter(() => new MySetMethodConverter()) + /// .BuildApp(); + /// + /// + public IReactiveUIBuilder WithSetMethodConverter(Func factory) + { + ArgumentExceptionHelper.ThrowIfNull(factory); + ConverterService.SetMethodConverters.Register(factory()); + return this; + } + + /// + /// Imports all converters from a Splat dependency resolver into the builder. + /// + /// The Splat resolver to import converters from. + /// The builder instance for chaining. + /// Thrown if resolver is null. + /// + /// + /// This is a migration helper to ease transition from Splat-based registration + /// to the new ConverterService-based registration. + /// + /// + /// This method imports all three converter types: + /// + /// Typed converters () + /// Fallback converters () + /// Set-method converters () + /// + /// + /// + /// + /// + /// // Import all converters from current Splat locator + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithConvertersFrom(AppLocator.Current) + /// .BuildApp(); + /// + /// + public IReactiveUIBuilder WithConvertersFrom(IReadonlyDependencyResolver resolver) + { + ArgumentExceptionHelper.ThrowIfNull(resolver); + + // Import typed converters + var typedConverters = resolver.GetServices(); + foreach (var converter in typedConverters) + { + if (converter is not null) + { + ConverterService.TypedConverters.Register(converter); + } + } + + // Import fallback converters + var fallbackConverters = resolver.GetServices(); + foreach (var converter in fallbackConverters) + { + if (converter is not null) + { + ConverterService.FallbackConverters.Register(converter); + } + } + + // Import set-method converters + var setMethodConverters = resolver.GetServices(); + foreach (var converter in setMethodConverters) + { + if (converter is not null) + { + ConverterService.SetMethodConverters.Register(converter); + } + } + + return this; + } + + /// + /// Configures a custom exception handler for unhandled errors in ReactiveUI observables. + /// + /// The custom exception handler to use. + /// The builder instance for chaining. + /// Thrown if exceptionHandler is null. + public IReactiveUIBuilder WithExceptionHandler(IObserver exceptionHandler) + { + _exceptionHandler = exceptionHandler ?? throw new ArgumentNullException(nameof(exceptionHandler)); + return this; + } + + /// + /// Configures the non-generic suspension host for application lifecycle management. + /// + /// The builder instance for chaining. + public IReactiveUIBuilder WithSuspensionHost() + { + _suspensionHost = new SuspensionHost(); + return this; + } + + /// + /// Configures a typed suspension host for application lifecycle management. + /// + /// The type of the application state to manage. + /// The builder instance for chaining. + public IReactiveUIBuilder WithSuspensionHost() + { + _suspensionHost = new SuspensionHost(); + return this; + } + + /// + /// Configures custom cache size limits for ReactiveUI's internal memoizing caches. + /// + /// The small cache limit to use (must be greater than 0). + /// The big cache limit to use (must be greater than 0). + /// The builder instance for chaining. + /// Thrown if either cache limit is less than or equal to 0. + public IReactiveUIBuilder WithCacheSizes(int smallCacheLimit, int bigCacheLimit) + { + if (smallCacheLimit <= 0) + { + throw new ArgumentOutOfRangeException(nameof(smallCacheLimit), "Small cache limit must be greater than 0."); + } + + if (bigCacheLimit <= 0) + { + throw new ArgumentOutOfRangeException(nameof(bigCacheLimit), "Big cache limit must be greater than 0."); + } + + _smallCacheLimit = smallCacheLimit; + _bigCacheLimit = bigCacheLimit; + return this; + } + /// /// Registers a custom view model with the dependency resolver. /// @@ -344,6 +679,20 @@ public IReactiveUIInstance BuildApp() throw new InvalidOperationException("Failed to create ReactiveUIInstance instance"); } + // Initialize static state (cache sizes, exception handler, suspension host) + InitializeStaticState(); + + // Set the global converter service + RxConverters.SetService(ConverterService); + + if (_messageBus is not null) + { + MessageBus.Current = _messageBus; + } + + // Mark ReactiveUI as initialized via builder pattern + RxAppBuilder.MarkAsInitialized(); + return appInstance; } @@ -935,6 +1284,163 @@ public IReactiveUIInstance WithInstance + /// Gets the platform-specific default small cache limit. + /// + /// The default small cache limit for the current platform. + private static int GetPlatformDefaultSmallCacheLimit() + { +#if ANDROID || IOS + return 32; +#else + return 64; +#endif + } + + /// + /// Gets the platform-specific default big cache limit. + /// + /// The default big cache limit for the current platform. + private static int GetPlatformDefaultBigCacheLimit() + { +#if ANDROID || IOS + return 64; +#else + return 256; +#endif + } + + /// + /// Registers all standard ReactiveUI converters to the ConverterService. + /// This mirrors the converters registered in Registrations.cs but targets the new ConverterService. + /// + private void RegisterStandardConverters() + { + // General converters + ConverterService.TypedConverters.Register(new EqualityTypeConverter()); + ConverterService.TypedConverters.Register(new StringConverter()); + + // Numeric → String converters + ConverterService.TypedConverters.Register(new ByteToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableByteToStringTypeConverter()); + ConverterService.TypedConverters.Register(new ShortToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableShortToStringTypeConverter()); + ConverterService.TypedConverters.Register(new IntegerToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableIntegerToStringTypeConverter()); + ConverterService.TypedConverters.Register(new LongToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableLongToStringTypeConverter()); + ConverterService.TypedConverters.Register(new SingleToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableSingleToStringTypeConverter()); + ConverterService.TypedConverters.Register(new DoubleToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableDoubleToStringTypeConverter()); + ConverterService.TypedConverters.Register(new DecimalToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableDecimalToStringTypeConverter()); + + // String → Numeric converters + ConverterService.TypedConverters.Register(new StringToByteTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableByteTypeConverter()); + ConverterService.TypedConverters.Register(new StringToShortTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableShortTypeConverter()); + ConverterService.TypedConverters.Register(new StringToIntegerTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableIntegerTypeConverter()); + ConverterService.TypedConverters.Register(new StringToLongTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableLongTypeConverter()); + ConverterService.TypedConverters.Register(new StringToSingleTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableSingleTypeConverter()); + ConverterService.TypedConverters.Register(new StringToDoubleTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableDoubleTypeConverter()); + ConverterService.TypedConverters.Register(new StringToDecimalTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableDecimalTypeConverter()); + + // Boolean ↔ String converters + ConverterService.TypedConverters.Register(new BooleanToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableBooleanToStringTypeConverter()); + ConverterService.TypedConverters.Register(new StringToBooleanTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableBooleanTypeConverter()); + + // Guid ↔ String converters + ConverterService.TypedConverters.Register(new GuidToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableGuidToStringTypeConverter()); + ConverterService.TypedConverters.Register(new StringToGuidTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableGuidTypeConverter()); + + // DateTime ↔ String converters + ConverterService.TypedConverters.Register(new DateTimeToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableDateTimeToStringTypeConverter()); + ConverterService.TypedConverters.Register(new StringToDateTimeTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableDateTimeTypeConverter()); + + // DateTimeOffset ↔ String converters + ConverterService.TypedConverters.Register(new DateTimeOffsetToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableDateTimeOffsetToStringTypeConverter()); + ConverterService.TypedConverters.Register(new StringToDateTimeOffsetTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableDateTimeOffsetTypeConverter()); + + // TimeSpan ↔ String converters + ConverterService.TypedConverters.Register(new TimeSpanToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableTimeSpanToStringTypeConverter()); + ConverterService.TypedConverters.Register(new StringToTimeSpanTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableTimeSpanTypeConverter()); + +#if NET6_0_OR_GREATER + // DateOnly ↔ String converters (.NET 6+) + ConverterService.TypedConverters.Register(new DateOnlyToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableDateOnlyToStringTypeConverter()); + ConverterService.TypedConverters.Register(new StringToDateOnlyTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableDateOnlyTypeConverter()); + + // TimeOnly ↔ String converters (.NET 6+) + ConverterService.TypedConverters.Register(new TimeOnlyToStringTypeConverter()); + ConverterService.TypedConverters.Register(new NullableTimeOnlyToStringTypeConverter()); + ConverterService.TypedConverters.Register(new StringToTimeOnlyTypeConverter()); + ConverterService.TypedConverters.Register(new StringToNullableTimeOnlyTypeConverter()); +#endif + + // Uri ↔ String converters + ConverterService.TypedConverters.Register(new UriToStringTypeConverter()); + ConverterService.TypedConverters.Register(new StringToUriTypeConverter()); + + // Nullable ↔ Non-Nullable converters + ConverterService.TypedConverters.Register(new ByteToNullableByteTypeConverter()); + ConverterService.TypedConverters.Register(new NullableByteToByteTypeConverter()); + ConverterService.TypedConverters.Register(new ShortToNullableShortTypeConverter()); + ConverterService.TypedConverters.Register(new NullableShortToShortTypeConverter()); + ConverterService.TypedConverters.Register(new IntegerToNullableIntegerTypeConverter()); + ConverterService.TypedConverters.Register(new NullableIntegerToIntegerTypeConverter()); + ConverterService.TypedConverters.Register(new LongToNullableLongTypeConverter()); + ConverterService.TypedConverters.Register(new NullableLongToLongTypeConverter()); + ConverterService.TypedConverters.Register(new SingleToNullableSingleTypeConverter()); + ConverterService.TypedConverters.Register(new NullableSingleToSingleTypeConverter()); + ConverterService.TypedConverters.Register(new DoubleToNullableDoubleTypeConverter()); + ConverterService.TypedConverters.Register(new NullableDoubleToDoubleTypeConverter()); + ConverterService.TypedConverters.Register(new DecimalToNullableDecimalTypeConverter()); + ConverterService.TypedConverters.Register(new NullableDecimalToDecimalTypeConverter()); + } + + /// + /// Initializes the static state for ReactiveUI based on builder configuration. + /// This includes cache sizes, exception handler, and suspension host. + /// + private void InitializeStaticState() + { + // Initialize cache sizes - use configured values or platform defaults + var smallCache = _smallCacheLimit ?? GetPlatformDefaultSmallCacheLimit(); + var bigCache = _bigCacheLimit ?? GetPlatformDefaultBigCacheLimit(); + RxCacheSize.Initialize(smallCache, bigCache); + + // Initialize exception handler if configured + if (_exceptionHandler is not null) + { + RxState.InitializeExceptionHandler(_exceptionHandler); + } + + // Initialize suspension host if configured + if (_suspensionHost is not null) + { + RxSuspension.InitializeSuspensionHost(_suspensionHost); + } + } + private void ConfigureSchedulers() => WithCustomRegistration(_ => { diff --git a/src/ReactiveUI/Builder/RxAppBuilder.cs b/src/ReactiveUI/Builder/RxAppBuilder.cs index b03ffb0418..62ccd6da7a 100644 --- a/src/ReactiveUI/Builder/RxAppBuilder.cs +++ b/src/ReactiveUI/Builder/RxAppBuilder.cs @@ -10,6 +10,14 @@ namespace ReactiveUI.Builder; /// public static class RxAppBuilder { +#if NET9_0_OR_GREATER + private static readonly System.Threading.Lock _resetLock = new(); +#else + private static readonly object _resetLock = new(); +#endif + + private static int _hasBeenInitialized; // 0 = false, 1 = true + /// /// Creates a ReactiveUI builder with the Splat Locator instance. /// @@ -32,4 +40,82 @@ public static ReactiveUIBuilder CreateReactiveUIBuilder(this IMutableDependencyR var readonlyResolver = resolver as IReadonlyDependencyResolver ?? AppLocator.Current; return new(resolver, readonlyResolver); } + + /// + /// Ensures ReactiveUI has been initialized via the builder pattern. + /// Throws an exception if BuildApp() has not been called. + /// + /// Thrown if ReactiveUI has not been initialized via the builder pattern. + /// + /// + /// This method replaces the old RxApp.EnsureInitialized() pattern. + /// Call this method at the start of your application or in test setup to verify ReactiveUI is properly initialized. + /// + /// + /// To initialize ReactiveUI, call: + /// + /// RxAppBuilder.CreateReactiveUIBuilder() + /// .WithCoreServices() + /// .BuildApp(); + /// + /// + /// + public static void EnsureInitialized() + { + lock (_resetLock) + { + if (_hasBeenInitialized == 0) + { + throw new InvalidOperationException( + "ReactiveUI has not been initialized. You must initialize ReactiveUI using the builder pattern. " + + "See https://www.reactiveui.net/docs/handbook/rxappbuilder.html for migration guidance.\n\n" + + "Example:\n" + + "RxAppBuilder.CreateReactiveUIBuilder()\n" + + " .WithCoreServices()\n" + + " .WithPlatformServices()\n" + + " .BuildApp();"); + } + } + } + + /// + /// Resets the initialization state of ReactiveUI. + /// This method is intended for testing purposes only. + /// + /// + /// WARNING: This method should ONLY be used in unit tests to reset state between test runs. + /// Never call this in production code as it can lead to inconsistent application state. + /// This method is thread-safe and performs all reset operations atomically. + /// + internal static void ResetForTesting() + { + lock (_resetLock) + { + // Reset schedulers back to defaults + RxSchedulers.ResetForTesting(); + + // Reset Splat builder state first + Splat.Builder.AppBuilder.ResetBuilderStateForTests(); + + // Reset the locator to a clean InstanceGenericFirstDependencyResolver + AppLocator.SetLocator(new InstanceGenericFirstDependencyResolver()); + + // Clear activation fetcher cache since it queries the AppLocator + ViewForMixins.ResetActivationFetcherCacheForTesting(); + + // Finally, reset the initialization flag + _hasBeenInitialized = 0; + } + } + + /// + /// Marks ReactiveUI as initialized. Called by ReactiveUIBuilder.BuildApp(). + /// + internal static void MarkAsInitialized() + { + lock (_resetLock) + { + _hasBeenInitialized = 1; + } + } } diff --git a/src/ReactiveUI/Expression/ExpressionRewriter.cs b/src/ReactiveUI/Expression/ExpressionRewriter.cs index 82e35127f9..067437812f 100644 --- a/src/ReactiveUI/Expression/ExpressionRewriter.cs +++ b/src/ReactiveUI/Expression/ExpressionRewriter.cs @@ -3,60 +3,69 @@ // 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; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; namespace ReactiveUI; /// -/// Class for simplifying and validating expressions. +/// Rewrites and validates expression trees used by ReactiveUI binding infrastructure, normalizing +/// supported constructs into a consistent shape. /// -internal class ExpressionRewriter : ExpressionVisitor +/// +/// +/// This visitor intentionally supports a constrained set of expression node types. Unsupported shapes +/// are rejected with actionable exceptions to help callers correct their expressions. +/// +/// +/// Supported rewrites include: +/// +/// +/// is rewritten into an indexer access (get_Item / Item). +/// to a special-name indexer method (get_Item) is rewritten into an . +/// is rewritten into member access to Length. +/// is stripped. +/// +/// +/// Index expressions are only supported when all indices are constants. +/// +/// +internal sealed class ExpressionRewriter : ExpressionVisitor { + /// + /// Visits the specified expression node and rewrites supported shapes into their normalized form. + /// + /// The expression node to visit. + /// The rewritten expression. + /// Thrown when is . + /// Thrown when uses an unsupported node type or shape. public override Expression Visit(Expression? node) { ArgumentExceptionHelper.ThrowIfNull(node); - switch (node!.NodeType) + return node.NodeType switch { - case ExpressionType.ArrayIndex: - return VisitBinary((BinaryExpression)node); - case ExpressionType.ArrayLength: - return VisitUnary((UnaryExpression)node); - case ExpressionType.Call: - return VisitMethodCall((MethodCallExpression)node); - case ExpressionType.Index: - return VisitIndex((IndexExpression)node); - case ExpressionType.MemberAccess: - return VisitMember((MemberExpression)node); - case ExpressionType.Parameter: - return VisitParameter((ParameterExpression)node); - case ExpressionType.Constant: - return VisitConstant((ConstantExpression)node); - case ExpressionType.Convert: - return VisitUnary((UnaryExpression)node); - default: - var errorMessageBuilder = new StringBuilder($"Unsupported expression of type '{node.NodeType}' {node}."); - - if (node is BinaryExpression binaryExpression) - { - errorMessageBuilder.Append(" Did you meant to use expressions '") - .Append(binaryExpression.Left) - .Append("' and '") - .Append(binaryExpression.Right) - .Append("'?"); - } - - throw new NotSupportedException(errorMessageBuilder.ToString()); - } + ExpressionType.ArrayIndex => VisitBinary((BinaryExpression)node), + ExpressionType.ArrayLength => VisitUnary((UnaryExpression)node), + ExpressionType.Call => VisitMethodCall((MethodCallExpression)node), + ExpressionType.Index => VisitIndex((IndexExpression)node), + ExpressionType.MemberAccess => VisitMember((MemberExpression)node), + ExpressionType.Parameter => VisitParameter((ParameterExpression)node), + ExpressionType.Constant => VisitConstant((ConstantExpression)node), + ExpressionType.Convert => VisitUnary((UnaryExpression)node), + _ => throw CreateUnsupportedNodeException(node) + }; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ExpressionRewriter uses reflection to access type properties which requires dynamic code generation")] - [RequiresUnreferencedCode("ExpressionRewriter uses reflection to access type properties which may require unreferenced code")] + [RequiresUnreferencedCode("Expression rewriting uses reflection over runtime types (e.g., Item/Length) which may be removed by trimming.")] + [RequiresDynamicCode("Expression rewriting uses reflection over runtime types and may not be compatible with AOT compilation.")] [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Third Party Code")] [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Third Party Code")] -#endif protected override Expression VisitBinary(BinaryExpression node) { if (node.Right is not ConstantExpression) @@ -64,76 +73,79 @@ protected override Expression VisitBinary(BinaryExpression node) throw new NotSupportedException("Array index expressions are only supported with constants."); } - var left = Visit(node.Left); - var right = Visit(node.Right); + var instance = Visit(node.Left); + var index = (ConstantExpression)Visit(node.Right); - // Translate arrayindex into normal index expression - return Expression.MakeIndex(left, GetItemProperty(left.Type), [right]); + if (instance.Type.IsArray) + { + return Expression.ArrayAccess(instance, index); + } + + // Translate arrayindex into a normal index expression using the indexer property. + return Expression.MakeIndex(instance, GetItemProperty(instance.Type), [index]); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ExpressionRewriter uses reflection to access type properties which requires dynamic code generation")] - [RequiresUnreferencedCode("ExpressionRewriter uses reflection to access type properties which may require unreferenced code")] + [RequiresUnreferencedCode("Expression rewriting uses reflection over runtime types (e.g., Item/Length) which may be removed by trimming.")] + [RequiresDynamicCode("Expression rewriting uses reflection over runtime types and may not be compatible with AOT compilation.")] [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Third Party Code")] [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Third Party Code")] -#endif protected override Expression VisitUnary(UnaryExpression node) { - if (node.NodeType == ExpressionType.ArrayLength && node.Operand is not null) + if (node.Operand is null) { - var expression = Visit(node.Operand); - - var memberInfo = GetLengthProperty(expression.Type); - - return memberInfo switch - { - null => throw new InvalidOperationException("Could not find valid information for the array length operator."), - _ => Expression.MakeMemberAccess(expression, memberInfo) - }; + throw new ArgumentException("Could not find a valid operand for the node.", nameof(node)); } - else if (node.NodeType == ExpressionType.Convert && node.Operand is not null) + + if (node.NodeType == ExpressionType.Convert) { + // Strip conversion nodes to keep expression chains stable. return Visit(node.Operand); } - else if (node.Operand is not null) - { - return node.Update(Visit(node?.Operand)); - } - else + + if (node.NodeType == ExpressionType.ArrayLength) { - throw new ArgumentException("Could not find a valid operand for the node.", nameof(node)); + var operand = Visit(node.Operand); + var lengthProperty = GetLengthProperty(operand.Type); + + return Expression.MakeMemberAccess(operand, lengthProperty); } + + return node.Update(Visit(node.Operand)); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ExpressionRewriter uses reflection to access type properties which requires dynamic code generation")] - [RequiresUnreferencedCode("ExpressionRewriter uses reflection to access type properties which may require unreferenced code")] + [RequiresUnreferencedCode("Expression rewriting uses reflection over runtime types (e.g., Item/Length) which may be removed by trimming.")] + [RequiresDynamicCode("Expression rewriting uses reflection over runtime types and may not be compatible with AOT compilation.")] [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Third Party Code")] [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Third Party Code")] -#endif protected override Expression VisitMethodCall(MethodCallExpression node) { - // Rewrite a method call to an indexer as an index expression - if (node.Arguments.Any(static e => e is not ConstantExpression) || !node.Method.IsSpecialName) + if (!node.Method.IsSpecialName || !AllConstant(node.Arguments)) { throw new NotSupportedException("Index expressions are only supported with constants."); } if (node.Object is null) { - throw new ArgumentException("The Method call does not point towards an object.", nameof(node)); + throw new ArgumentException("The method call does not point towards an object.", nameof(node)); } var instance = Visit(node.Object); - IEnumerable arguments = Visit(node.Arguments); - // Translate call to get_Item into normal index expression - return Expression.MakeIndex(instance, GetItemProperty(instance.Type), arguments); + // Visit arguments explicitly to avoid LINQ allocations. + var args = VisitArgumentList(node.Arguments); + + return Expression.MakeIndex(instance, GetItemProperty(instance.Type), args); } + /// + /// Validates that index expressions only use constant arguments, then defers to the base visitor. + /// + /// The index expression. + /// The visited (and potentially rewritten) index expression. + /// Thrown when any index argument is not a constant. protected override Expression VisitIndex(IndexExpression node) { - if (node.Arguments.Any(static e => e is not ConstantExpression)) + if (!AllConstant(node.Arguments)) { throw new NotSupportedException("Index expressions are only supported with constants."); } @@ -141,21 +153,100 @@ protected override Expression VisitIndex(IndexExpression node) return base.VisitIndex(node); } -#if NET6_0_OR_GREATER - private static PropertyInfo? GetItemProperty([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) -#else - private static PropertyInfo? GetItemProperty(Type type) -#endif + /// + /// Creates a consistent exception for unsupported node types, including additional context for binary expressions. + /// + /// The unsupported node. + /// An exception to throw. + private static Exception CreateUnsupportedNodeException(Expression node) + { + // Preserve prior behavior: include helpful guidance for binary expressions. + var sb = new StringBuilder(96); + sb.Append("Unsupported expression of type '") + .Append(node.NodeType) + .Append("' ") + .Append(node) + .Append('.'); + + if (node is BinaryExpression be) + { + sb.Append(" Did you meant to use expressions '") + .Append(be.Left) + .Append("' and '") + .Append(be.Right) + .Append("'?"); + } + + return new NotSupportedException(sb.ToString()); + } + + /// + /// Returns the indexer property (Item) for the specified type. + /// + /// The type to inspect. + /// The resolved indexer property. + /// Thrown when no indexer property can be found. + private static PropertyInfo GetItemProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] + Type type) + { + // NOTE: Using the Type instance preserves trimming annotations; do not reconstruct Type from RuntimeTypeHandle. + var property = type.GetRuntimeProperty("Item"); + return property ?? throw new InvalidOperationException("Could not find a valid indexer property named 'Item'."); + } + + /// + /// Returns the Length property for the specified type. + /// + /// The type to inspect. + /// The resolved length property. + /// Thrown when no length property can be found. + private static PropertyInfo GetLengthProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] + Type type) { - return type.GetRuntimeProperty("Item"); + // NOTE: Using the Type instance preserves trimming annotations; do not reconstruct Type from RuntimeTypeHandle. + var property = type.GetRuntimeProperty("Length"); + return property ?? throw new InvalidOperationException("Could not find valid information for the array length operator."); } -#if NET6_0_OR_GREATER - private static PropertyInfo? GetLengthProperty([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) -#else - private static PropertyInfo? GetLengthProperty(Type type) -#endif + /// + /// Determines whether all expressions in the provided collection are constant expressions. + /// + /// The argument list. + /// if all arguments are constants; otherwise . + private static bool AllConstant(ReadOnlyCollection expressions) { - return type.GetRuntimeProperty("Length"); + for (var i = 0; i < expressions.Count; i++) + { + if (expressions[i] is not ConstantExpression) + { + return false; + } + } + + return true; + } + + /// + /// Visits a method argument list without LINQ allocations. + /// + /// The argument list to visit. + /// A visited argument array suitable for . + private Expression[] VisitArgumentList(ReadOnlyCollection arguments) + { + var count = arguments.Count; + if (count == 0) + { + return Array.Empty(); + } + + var visited = new Expression[count]; + for (var i = 0; i < count; i++) + { + visited[i] = Visit(arguments[i]); + } + + return visited; } } diff --git a/src/ReactiveUI/Expression/Reflection.cs b/src/ReactiveUI/Expression/Reflection.cs index 3eb094019d..b5a357605d 100644 --- a/src/ReactiveUI/Expression/Reflection.cs +++ b/src/ReactiveUI/Expression/Reflection.cs @@ -3,191 +3,249 @@ // 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.Reflection; +using System.Runtime.CompilerServices; using System.Text; namespace ReactiveUI; /// -/// Helper class for handling Reflection amd Expression tree related items. +/// Helper class for handling reflection and expression-tree related operations. /// +/// +/// +/// This type is part of ReactiveUI's infrastructure and is used by binding, observation, +/// and other reflection-heavy code paths. +/// +/// +/// Trimming/AOT note: Some APIs in this type are inherently trimming-unfriendly (e.g. string-based type resolution +/// and expression-driven member traversal). Such APIs are annotated accordingly. +/// +/// [Preserve(AllMembers = true)] public static class Reflection { - [SuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Marked as Preserve")] - [SuppressMessage("Trimming", "IL2026:Calling members annotated with 'RequiresUnreferencedCodeAttribute' may break functionality when trimming application code.", Justification = "Marked as Preserve")] + /// + /// Cached expression rewriter used to simplify expression trees. + /// + /// + /// This instance is cached for performance. It is assumed that ExpressionRewriter is thread-safe for Visit. + /// If that assumption is invalid, this should be changed to a per-call instance or an object pool. + /// private static readonly ExpressionRewriter _expressionRewriter = new(); - [SuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Marked as Preserve")] - [SuppressMessage("Trimming", "IL2026:Calling members annotated with 'RequiresUnreferencedCodeAttribute' may break functionality when trimming application code.", Justification = "Marked as Preserve")] - private static readonly MemoizingMRUCache _typeCache = new( - static (type, _) => GetTypeHelper(type), - 20); + /// + /// Cache for mapping type names to resolved instances. + /// + /// + /// Initialized lazily by to keep trimming-risky initialization inside the RUC boundary. + /// + private static MemoizingMRUCache? _typeCache; /// - /// Uses the expression re-writer to simplify the Expression down to it's simplest Expression. + /// Uses the expression re-writer to simplify the expression down to its simplest expression. /// /// The expression to rewrite. - /// The rewritten expression. + /// The rewritten expression, or if is . public static Expression Rewrite(Expression? expression) => _expressionRewriter.Visit(expression); /// - /// Will convert a Expression which points towards a property - /// to a string containing the property names. - /// The sub-properties will be separated by the '.' character. - /// Index based values will include [] after the name. + /// Converts an expression that points to a property chain into a dotted path string. + /// Sub-properties are separated by '.'. + /// Index-based values include [] after the name, with the index argument values. /// /// The expression to generate the property names from. - /// A string form for the property the expression is pointing to. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Expression tree analysis requires dynamic code generation")] - [RequiresUnreferencedCode("Expression tree analysis may reference members that could be trimmed")] -#endif + /// A string representation for the property chain the expression points to. + /// Thrown when is . + /// + /// This method intentionally follows existing behavior, including the assumption that index arguments are + /// instances. + /// public static string ExpressionToPropertyNames(Expression? expression) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(expression); + var sb = new StringBuilder(); + var firstSegment = true; foreach (var exp in expression!.GetExpressionChain()) { - if (exp.NodeType != ExpressionType.Parameter) + if (exp.NodeType == ExpressionType.Parameter) { - // Indexer expression - if (exp.NodeType == ExpressionType.Index && exp is IndexExpression indexExpression && indexExpression.Indexer is not null) - { - sb.Append(indexExpression.Indexer.Name).Append('['); + continue; + } + + if (!firstSegment) + { + sb.Append('.'); + } + + if (exp.NodeType == ExpressionType.Index && + exp is IndexExpression indexExpression && + indexExpression.Indexer is not null) + { + sb.Append(indexExpression.Indexer.Name).Append('['); - foreach (var argument in indexExpression.Arguments) + var args = indexExpression.Arguments; + for (var i = 0; i < args.Count; i++) + { + if (i != 0) { - sb.Append(((ConstantExpression)argument).Value).Append(','); + sb.Append(','); } - sb.Replace(',', ']', sb.Length - 1, 1); - } - else if (exp.NodeType == ExpressionType.MemberAccess && exp is MemberExpression memberExpression) - { - sb.Append(memberExpression.Member.Name); + // Preserve original behavior: assumes ConstantExpression. + sb.Append(((ConstantExpression)args[i]).Value); } - } - sb.Append('.'); - } + sb.Append(']'); + } + else if (exp.NodeType == ExpressionType.MemberAccess && exp is MemberExpression memberExpression) + { + sb.Append(memberExpression.Member.Name); + } - if (sb.Length > 0) - { - sb.Remove(sb.Length - 1, 1); + firstSegment = false; } return sb.ToString(); } /// - /// Converts a MemberInfo into a Func which will fetch the value for the Member. - /// Handles either fields or properties. + /// Converts a into a delegate which fetches the value for the member. + /// Supports fields and properties. /// /// The member info to convert. - /// A Func that takes in the object/indexes and returns the value. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Member access requires dynamic code generation")] - [RequiresUnreferencedCode("Member access may reference members that could be trimmed")] -#endif + /// + /// A delegate that takes (target, indexArguments) and returns the value; or + /// when the member is not a field or property. + /// + /// Thrown when is . + /// + /// + /// For fields, the existing behavior throws if the field value is . + /// + /// + /// Trimming note: this method does not discover members by name; it operates on an already-resolved . + /// + /// public static Func? GetValueFetcherForProperty(MemberInfo? member) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(member); - var field = member as FieldInfo; - if (field is not null) + if (member is FieldInfo field) + { + // Delegate captures 'field' (setup-time), avoiding repeated dynamic checks. + return (obj, _) => + { + var value = field.GetValue(obj); + return value ?? throw new InvalidOperationException(); + }; + } + + if (member is PropertyInfo property) { - return (obj, _) => field.GetValue(obj) ?? throw new InvalidOperationException(); + return property.GetValue; } - var property = member as PropertyInfo; - return property!.GetValue; + return null; } /// - /// Converts a MemberInfo into a Func which will fetch the value for the Member. - /// Handles either fields or properties. - /// If there is no field or property with the matching MemberInfo it'll throw - /// an ArgumentException. + /// Converts a into a delegate which fetches the value for the member. + /// Supports fields and properties and throws if the member is not supported. /// /// The member info to convert. - /// A Func that takes in the object/indexes and returns the value. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Member access requires dynamic code generation")] - [RequiresUnreferencedCode("Member access may reference members that could be trimmed")] -#endif + /// A delegate that takes (target, indexArguments) and returns the value. + /// Thrown when is . + /// Thrown when is not a field or property. public static Func GetValueFetcherOrThrow(MemberInfo? member) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(member); var ret = GetValueFetcherForProperty(member); - return ret ?? throw new ArgumentException($"Type '{member!.DeclaringType}' must have a property '{member.Name}'"); } /// - /// Converts a MemberInfo into a Func which will set the value for the Member. - /// Handles either fields or properties. - /// If there is no field or property with the matching MemberInfo it'll throw - /// an ArgumentException. + /// Converts a into a delegate which sets the value for the member. + /// Supports fields and properties. /// /// The member info to convert. - /// A Func that takes in the object/indexes and sets the value. - public static Action GetValueSetterForProperty(MemberInfo? member) // TODO: Create Test + /// + /// A delegate that takes (target, value, indexArguments) and sets the value; or + /// when the member is not a field or property. + /// + /// Thrown when is . + /// + /// + /// This is the soft-fail setter API. It must not throw when the member is unsupported, + /// because callers (including compiled chain setters) rely on it for shouldThrow == false paths. + /// + /// + /// Trimming note: this method does not discover members by name; it operates on an already-resolved . + /// + /// + public static Action? GetValueSetterForProperty(MemberInfo? member) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(member); - var field = member as FieldInfo; - if (field is not null) + if (member is FieldInfo field) { return (obj, val, _) => field.SetValue(obj, val); } - var property = member as PropertyInfo; - return property!.SetValue; + if (member is PropertyInfo property) + { + return property.SetValue; + } + + return null; } /// - /// Converts a MemberInfo into a Func which will set the value for the Member. - /// Handles either fields or properties. - /// If there is no field or property with the matching MemberInfo it'll throw - /// an ArgumentException. + /// Converts a into a delegate which sets the value for the member. + /// Supports fields and properties and throws if the member is not supported. /// /// The member info to convert. - /// A Func that takes in the object/indexes and sets the value. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Member access requires dynamic code generation")] - [RequiresUnreferencedCode("Member access may reference members that could be trimmed")] -#endif - public static Action? GetValueSetterOrThrow(MemberInfo? member) // TODO: Create Test + /// A delegate that takes (target, value, indexArguments) and sets the value. + /// Thrown when is . + /// Thrown when is not a field or property. + public static Action GetValueSetterOrThrow(MemberInfo? member) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(member); var ret = GetValueSetterForProperty(member); - return ret ?? throw new ArgumentException($"Type '{member!.DeclaringType}' must have a property '{member.Name}'"); } /// - /// Based on a list of Expressions get the value of the last property in the chain if possible. - /// The Expressions are typically property chains. Eg Property1.Property2.Property3 - /// The method will make sure that each Expression can get a value along the way - /// and get each property until each expression is evaluated. + /// Based on a list of expressions, attempts to get the value of the last property in the chain. /// - /// A output value where to store the value if the value can be fetched. + /// The expected type of the final value. + /// Receives the value if the chain can be evaluated. /// The object that starts the property chain. - /// A list of expressions which will point towards a property or field. - /// The type of the end value we are trying to get. - /// If the value was successfully retrieved or not. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] -#endif + /// A sequence of expressions that point to properties/fields. + /// if the value was successfully retrieved; otherwise . + /// Thrown when is empty. + /// Thrown when is . + /// + /// Trimming note: this method may traverse arbitrary member chains represented by expressions; it is not possible + /// to express a complete trimming contract locally. + /// + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TryGetValueForPropertyChain(out TValue changeValue, object? current, IEnumerable expressionChain) // TODO: Create Test { - var expressions = expressionChain.ToList(); - foreach (var expression in expressions.SkipLast(1)) + var expressions = MaterializeExpressions(expressionChain); + var count = expressions.Length; + + if (count == 0) + { + throw new InvalidOperationException("Expression chain must contain at least one element."); + } + + for (var i = 0; i < count - 1; i++) { if (current is null) { @@ -195,6 +253,7 @@ public static bool TryGetValueForPropertyChain(out TValue changeValue, o return false; } + var expression = expressions[i]; current = GetValueFetcherOrThrow(expression.GetMemberInfo())(current, expression.GetArgumentsArray()); } @@ -203,39 +262,42 @@ public static bool TryGetValueForPropertyChain(out TValue changeValue, o changeValue = default!; return false; } -#if NET6_0_OR_GREATER - var lastExpression = expressions[^1]; -#else -#pragma warning disable RCS1246 // Use element access - var lastExpression = expressions.Last(); -#pragma warning restore RCS1246 // Use element access -#endif + + var lastExpression = expressions[count - 1]; changeValue = (TValue)GetValueFetcherOrThrow(lastExpression.GetMemberInfo())(current, lastExpression.GetArgumentsArray())!; return true; } /// - /// Based on a list of Expressions get a IObservedChanged for the value - /// of the last property in the chain if possible. - /// The Expressions are property chains. Eg Property1.Property2.Property3 - /// The method will make sure that each Expression can get a value along the way - /// and get each property until each expression is evaluated. + /// Based on a list of expressions, attempts to produce an array of + /// values representing each step in the property chain. /// - /// A IObservedChanged for the value. + /// Receives an array with one entry per expression in the chain. /// The object that starts the property chain. - /// A list of expressions which will point towards a property or field. - /// If the value was successfully retrieved or not. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] -#endif + /// A sequence of expressions that point to properties/fields. + /// if all values were successfully retrieved; otherwise . + /// Thrown when is empty. + /// Thrown when is . + /// + /// This preserves the existing behavior: on early failure, the method writes a single + /// element at the failing index and returns . + /// + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TryGetAllValuesForPropertyChain(out IObservedChange[] changeValues, object? current, IEnumerable expressionChain) // TODO: Create Test { + var expressions = MaterializeExpressions(expressionChain); + var count = expressions.Length; + + changeValues = new IObservedChange[count]; + + if (count == 0) + { + throw new InvalidOperationException("Expression chain must contain at least one element."); + } + var currentIndex = 0; - var expressions = expressionChain.ToList(); - changeValues = new IObservedChange[expressions.Count]; - foreach (var expression in expressions.SkipLast(1)) + for (; currentIndex < count - 1; currentIndex++) { if (current is null) { @@ -243,10 +305,10 @@ public static bool TryGetAllValuesForPropertyChain(out IObservedChange(sender, expression, current); - currentIndex++; } if (current is null) @@ -255,42 +317,55 @@ public static bool TryGetAllValuesForPropertyChain(out IObservedChange(current, lastExpression, GetValueFetcherOrThrow(lastExpression.GetMemberInfo())(current, lastExpression.GetArgumentsArray())); + var lastExpression = expressions[count - 1]; + changeValues[currentIndex] = new ObservedChange( + current, + lastExpression, + GetValueFetcherOrThrow(lastExpression.GetMemberInfo())(current, lastExpression.GetArgumentsArray())); + return true; } /// - /// Based on a list of Expressions set a value - /// of the last property in the chain if possible. - /// The Expressions are property chains. Eg Property1.Property2.Property3 - /// The method will make sure that each Expression can use each value along the way - /// and set the last value. + /// Based on a list of expressions, attempts to set the value of the last property in the chain. /// + /// The type of the end value being set. /// The object that starts the property chain. - /// A list of expressions which will point towards a property or field. - /// The value to set on the last property in the Expression chain. - /// If we should throw if we are unable to set the value. - /// The type of the end value we are trying to set. - /// If the value was successfully retrieved or not. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] -#endif + /// A sequence of expressions that point to properties/fields. + /// The value to set on the last property in the chain. + /// + /// If , throw when reflection members are missing; otherwise fail softly. + /// + /// if the value was successfully set; otherwise . + /// Thrown when is empty. + /// + /// Thrown when is and traversal is required. + /// + /// + /// Thrown when is and a required member is not settable. + /// + /// + /// Trimming note: this method may traverse arbitrary member chains represented by expressions; it is not possible + /// to express a complete trimming contract locally. + /// + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TrySetValueToPropertyChain(object? target, IEnumerable expressionChain, TValue value, bool shouldThrow = true) // TODO: Create Test { - var expressions = expressionChain.ToList(); - foreach (var expression in expressions.SkipLast(1)) + var expressions = MaterializeExpressions(expressionChain); + var count = expressions.Length; + + if (count == 0) { - var getter = shouldThrow ? - GetValueFetcherOrThrow(expression.GetMemberInfo()) : - GetValueFetcherForProperty(expression.GetMemberInfo()); + throw new InvalidOperationException("Expression chain must contain at least one element."); + } + + for (var i = 0; i < count - 1; i++) + { + var expression = expressions[i]; + + var getter = shouldThrow + ? GetValueFetcherOrThrow(expression.GetMemberInfo()) + : GetValueFetcherForProperty(expression.GetMemberInfo()); if (getter is not null) { @@ -303,16 +378,11 @@ public static bool TrySetValueToPropertyChain(object? target, IEnumerabl return false; } -#if NET6_0_OR_GREATER - var lastExpression = expressions[^1]; -#else -#pragma warning disable RCS1246 // Use element access - var lastExpression = expressions.Last(); -#pragma warning restore RCS1246 // Use element access -#endif - var setter = shouldThrow ? - GetValueSetterOrThrow(lastExpression.GetMemberInfo()) : - GetValueSetterForProperty(lastExpression.GetMemberInfo()); + var lastExpression = expressions[count - 1]; + + var setter = shouldThrow + ? GetValueSetterOrThrow(lastExpression.GetMemberInfo()) + : GetValueSetterForProperty(lastExpression.GetMemberInfo()); if (setter is null) { @@ -324,81 +394,157 @@ public static bool TrySetValueToPropertyChain(object? target, IEnumerabl } /// - /// Gets a Type from the specified type name. - /// Uses a cache to avoid having to use Reflection every time. + /// Gets a from the specified type name, using a cache to avoid repeated reflection. /// /// The name of the type. - /// If we should throw an exception if the type can't be found. - /// The type that was found or null. - /// If we were unable to find the type. + /// If , throw when the type cannot be found. + /// + /// The resolved , or if not found and is . + /// + /// + /// Thrown when the type cannot be found and is . + /// + /// + /// Trimming note: resolving types by string name is inherently trimming-unfriendly unless additional metadata is preserved externally. + /// + [RequiresUnreferencedCode("Resolves types by name and loads assemblies; types may be trimmed.")] public static Type? ReallyFindType(string? type, bool throwOnFailure) // TODO: Create Test { - var ret = _typeCache.Get(type ?? string.Empty); + var cache = Volatile.Read(ref _typeCache); + if (cache is null) + { + // Create inside the RUC boundary to avoid analyzer warnings from static initialization. + var created = new MemoizingMRUCache( + static (typeName, _) => GetTypeHelper(typeName), + 20); + + cache = Interlocked.CompareExchange(ref _typeCache, created, null) ?? created; + } + + var ret = cache.Get(type ?? string.Empty); return ret is not null || !throwOnFailure ? ret : throw new TypeLoadException(); } /// - /// Gets the appropriate EventArgs derived object for the specified event name for a Type. + /// Gets the appropriate -derived type for the specified event name on a . /// - /// The type of object to find the event on. + /// The type of object to find the event on. Must preserve public events under trimming. /// The name of the event. - /// The Type of the EventArgs to use. - /// If there is no event matching the name on the target type. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Event access may reference members that could be trimmed")] -#endif + /// The type of the event args used by the event handler. + /// Thrown when is . + /// Thrown if there is no event matching the name on the target type. + /// Thrown if the event handler type does not expose an Invoke method. + /// + /// Trimming note: the event handler type is obtained from , which does not carry + /// annotations. This prevents expressing a complete trimming contract here. + /// + [RequiresUnreferencedCode("Reflects over custom delegate Invoke signature; members may be trimmed.")] public static Type GetEventArgsTypeForEvent( -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] -#endif - Type type, - string? eventName) // TODO: Create Test + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, + string? eventName) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(type); - var ti = type; - var ei = ti.GetRuntimeEvent(eventName!); - if (ei is null || ei.EventHandlerType is null) + var eventInfo = type.GetRuntimeEvent(eventName!); + if (eventInfo is null || eventInfo.EventHandlerType is null) { throw new Exception($"Couldn't find {type.FullName}.{eventName}"); } - // Find the EventArgs type parameter of the event via digging around via reflection - return ei.EventHandlerType.GetRuntimeMethods().First(static x => x.Name == "Invoke").GetParameters()[1].ParameterType; + // Faster and allocation-free: do not enumerate runtime methods. + var invoke = eventInfo.EventHandlerType.GetMethod("Invoke") ?? throw new MissingMethodException(eventInfo.EventHandlerType.FullName, "Invoke"); + var parameters = invoke.GetParameters(); + return parameters[1].ParameterType; + } + + /// + /// Checks to make sure that the specified method names on the target type are overridden. + /// + /// The name of the calling type. + /// The type to check. Must preserve public and non-public methods under trimming. + /// The method names to check. + /// Thrown if any method is not overridden on the target type. + /// + /// Trimming note: this method inspects declared method names; the parameter is annotated accordingly. + /// + public static void ThrowIfMethodsNotOverloaded( + string callingTypeName, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + Type targetType, + params string[] methodsToCheck) // TODO: Create Test + { + ArgumentExceptionHelper.ThrowIfNull(methodsToCheck); + + var methods = targetType.GetTypeInfo().DeclaredMethods; + + for (var i = 0; i < methodsToCheck.Length; i++) + { + var name = methodsToCheck[i]; + MethodInfo? found = null; + + foreach (var m in methods) + { + if (string.Equals(m.Name, name, StringComparison.Ordinal)) + { + found = m; + break; + } + } + + if (found is null) + { + throw new Exception($"Your class must implement {name} and call {callingTypeName}.{name}"); + } + } } /// - /// Checks to make sure that the specified method names on the target object - /// are overriden. + /// Checks to make sure that the specified method names on the target object are overridden. /// /// The name of the calling type. /// The object to check. - /// The name of the methods to check. - /// Thrown if the methods aren't overriden on the target object. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Method access may reference members that could be trimmed")] -#endif + /// The method names to check. + /// Thrown when is . + /// Thrown if any method is not overridden on the target object. + /// + /// Trimming note: the runtime type is discovered dynamically via , so this method + /// cannot express a complete trimming contract locally. + /// + [RequiresUnreferencedCode("Inspects declared methods on a runtime type; members may be trimmed.")] public static void ThrowIfMethodsNotOverloaded(string callingTypeName, object targetObject, params string[] methodsToCheck) // TODO: Create Test { - var (methodName, methodImplementation) = methodsToCheck - .Select(x => - { - var methods = targetObject.GetType().GetTypeInfo().DeclaredMethods; - return (methodName: x, methodImplementation: methods.FirstOrDefault(y => y.Name == x)); - }) - .FirstOrDefault(x => x.methodImplementation is null); - - if (methodName != default) + ArgumentExceptionHelper.ThrowIfNull(targetObject); + ArgumentExceptionHelper.ThrowIfNull(methodsToCheck); + + var methods = targetObject.GetType().GetTypeInfo().DeclaredMethods; + + for (var i = 0; i < methodsToCheck.Length; i++) { - throw new Exception($"Your class must implement {methodName} and call {callingTypeName}.{methodName}"); + var name = methodsToCheck[i]; + MethodInfo? found = null; + + foreach (var m in methods) + { + if (string.Equals(m.Name, name, StringComparison.Ordinal)) + { + found = m; + break; + } + } + + if (found is null) + { + throw new Exception($"Your class must implement {name} and call {callingTypeName}.{name}"); + } } } /// - /// Determines if the specified property is static or not. + /// Determines if the specified property is static. /// /// The property information to check. - /// If the property is static or not. + /// if the property is static; otherwise . + /// Thrown when is . public static bool IsStatic(this PropertyInfo item) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(item); @@ -407,10 +553,20 @@ public static bool IsStatic(this PropertyInfo item) // TODO: Create Test return method.IsStatic; } -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("ViewModelWhenAnyValue may reference types that could be trimmed")] - [RequiresDynamicCode("ViewModelWhenAnyValue uses reflection which requires dynamic code generation")] -#endif + /// + /// Creates an observable that switches to observing the provided expression on the current ViewModel. + /// + /// The view type. + /// The view model type. + /// The current view model (not used directly; preserved for signature compatibility). + /// The view instance. + /// The expression to observe dynamically. + /// An observable that emits values produced by the dynamic observation. + /// + /// Trimming note: dynamic observation via expression trees typically requires reflection over members + /// that may be trimmed; callers should preserve metadata for observed members. + /// + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] internal static IObservable ViewModelWhenAnyValue(TViewModel? viewModel, TView view, Expression? expression) where TView : class, IViewFor where TViewModel : class => @@ -419,28 +575,346 @@ internal static IObservable ViewModelWhenAnyValue(TVi .Select(x => ((TViewModel?)x).WhenAnyDynamic(expression, y => y.Value)) .Switch()!; -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Method access may reference members that could be trimmed")] -#endif - private static Type? GetTypeHelper(string type) => Type.GetType( - type, - assemblyName => + /// + /// Attempts to resolve a type name using + /// with custom assembly resolution that first searches loaded assemblies and then tries to load by name. + /// + /// The type name. + /// The resolved type or . + /// + /// Trimming note: this is string-based type resolution and may fail under trimming without explicit preservation. + /// + [RequiresUnreferencedCode("Resolves types by name and loads assemblies; types may be trimmed.")] + private static Type? GetTypeHelper(string type) => + Type.GetType( + type, + assemblyName => + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + for (var i = 0; i < assemblies.Length; i++) + { + var a = assemblies[i]; + if (a.FullName == assemblyName.FullName) + { + return a; + } + } + + try + { + return Assembly.Load(assemblyName); + } + catch + { + return null; + } + }, + null, + false); + + /// + /// Materializes an expression chain into an array to enable index-based iteration without LINQ. + /// + /// The expression chain to materialize. + /// An array containing the expressions in enumeration order. + /// Thrown when is . + /// + /// This helper is used to reduce allocations and virtual dispatch in hot paths by enabling for-loop iteration. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Expression[] MaterializeExpressions(IEnumerable expressionChain) + { + ArgumentExceptionHelper.ThrowIfNull(expressionChain); + + if (expressionChain is Expression[] arr) + { + return arr; + } + + if (expressionChain is ICollection coll) + { + if (coll.Count == 0) + { + return Array.Empty(); + } + + var result = new Expression[coll.Count]; + coll.CopyTo(result, 0); + return result; + } + + return expressionChain.ToArray(); + } + + /// + /// Pre-compiled property chain that caches getter delegates and indexer arguments. + /// + /// The root type expected by the expression chain. + /// The final value type. + /// + /// + /// This type exists to move expression-chain enumeration and member resolution out of observable hot paths. + /// After construction, and execute using cached delegates. + /// + /// + /// Trimming note: constructing this type typically involves expression parsing and may be trimming-sensitive; + /// callers should treat construction as the “reflection boundary”. + /// + /// + internal sealed class CompiledPropertyChain + { + /// + /// Cached getter delegates for each expression step. + /// + private readonly Func[] _getters; + + /// + /// Cached argument arrays for each expression step (indexers); entries may be . + /// + private readonly object?[]?[] _arguments; + + /// + /// Cached expressions for each step, used for constructing observed changes. + /// + private readonly Expression[] _expressions; + + /// + /// Initializes a new instance of the class. + /// + /// The expression chain to compile. Must contain at least one expression. + /// Thrown when is . + /// Thrown when is empty. + public CompiledPropertyChain(Expression[] expressionChain) + { + ArgumentExceptionHelper.ThrowIfNull(expressionChain); + + if (expressionChain.Length == 0) + { + throw new InvalidOperationException("Expression chain must contain at least one element."); + } + + _expressions = expressionChain; + _getters = new Func[expressionChain.Length]; + _arguments = new object?[]?[expressionChain.Length]; + + for (var i = 0; i < expressionChain.Length; i++) + { + var expr = expressionChain[i]; + _getters[i] = GetValueFetcherOrThrow(expr.GetMemberInfo()); + _arguments[i] = expr.GetArgumentsArray(); + } + } + + /// + /// Attempts to get the final value from the property chain. + /// + /// The root object. + /// Receives the final value when successful. + /// if successful; otherwise . + public bool TryGetValue(TSource? source, out TValue value) { - var assembly = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), z => z.FullName == assemblyName.FullName); - if (assembly is not null) + object? current = source; + var lastIndex = _getters.Length - 1; + + for (var i = 0; i < lastIndex; i++) + { + if (current is null) + { + value = default!; + return false; + } + + current = _getters[i](current, _arguments[i]); + } + + if (current is null) { - return assembly; + value = default!; + return false; } - try + value = (TValue)_getters[lastIndex](current, _arguments[lastIndex])!; + return true; + } + + /// + /// Attempts to get all intermediate values in the property chain as observed changes. + /// + /// The root object. + /// Receives an array with one entry per expression in the chain. + /// if successful; otherwise . + /// + /// Mirrors behavior: on early failure at index i, + /// writes changeValues[i] = null! and returns . + /// + public bool TryGetAllValues(TSource? source, out IObservedChange[] changeValues) + { + var count = _expressions.Length; + changeValues = new IObservedChange[count]; + + object? current = source; + var lastIndex = count - 1; + + for (var i = 0; i < lastIndex; i++) { - return Assembly.Load(assemblyName); + if (current is null) + { + changeValues[i] = null!; + return false; + } + + var sender = current; + current = _getters[i](current, _arguments[i]); + changeValues[i] = new ObservedChange(sender, _expressions[i], current); } - catch + + if (current is null) { - return null; + changeValues[lastIndex] = null!; + return false; } - }, - null, - false); + + changeValues[lastIndex] = new ObservedChange( + current, + _expressions[lastIndex], + _getters[lastIndex](current, _arguments[lastIndex])); + + return true; + } + } + + /// + /// Pre-compiled setter for a property chain. + /// + /// The root type expected by the expression chain. + /// The value type to set. + /// + /// + /// This type is designed for binding hot paths: traversal and setter invocation is performed using cached delegates. + /// It does not synthesize for intermediate nulls; it follows “Try*” semantics. + /// + /// + /// Trimming note: construction is typically the “reflection boundary”. + /// + /// + internal sealed class CompiledPropertyChainSetter + { + /// + /// Cached getter delegates used to walk from the root to the parent of the final member. + /// + private readonly Func[] _parentGetters; + + /// + /// Cached argument arrays for each parent step (indexers); entries may be . + /// + private readonly object?[]?[] _parentArguments; + + /// + /// Cached setter delegate for the final member; may be if the member is not settable. + /// + private readonly Action? _setter; + + /// + /// Cached argument array for the final setter (indexer); may be . + /// + private readonly object?[]? _setterArguments; + + /// + /// Cached error message for throwing when the final member is not settable. + /// + private readonly string _unsettableMemberMessage; + + /// + /// Initializes a new instance of the class. + /// + /// The expression chain to compile. Must contain at least one expression. + /// Thrown when is . + /// Thrown when is empty. + public CompiledPropertyChainSetter(Expression[] expressionChain) + { + ArgumentExceptionHelper.ThrowIfNull(expressionChain); + + if (expressionChain.Length == 0) + { + throw new InvalidOperationException("Expression chain must contain at least one element."); + } + + if (expressionChain.Length == 1) + { + _parentGetters = Array.Empty>(); + _parentArguments = Array.Empty(); + } + else + { + var parentCount = expressionChain.Length - 1; + _parentGetters = new Func[parentCount]; + _parentArguments = new object?[]?[parentCount]; + + for (var i = 0; i < parentCount; i++) + { + var expr = expressionChain[i]; + _parentGetters[i] = GetValueFetcherOrThrow(expr.GetMemberInfo()); + _parentArguments[i] = expr.GetArgumentsArray(); + } + } + + var lastExpr = expressionChain[expressionChain.Length - 1]; + _setter = GetValueSetterForProperty(lastExpr.GetMemberInfo()); + _setterArguments = lastExpr.GetArgumentsArray(); + + // Preserve legacy-style message format used by OrThrow helpers (type + member name). + var member = lastExpr.GetMemberInfo(); + _unsettableMemberMessage = $"Type '{member?.DeclaringType}' must have a property '{member?.Name}'"; + } + + /// + /// Attempts to set the value at the end of the property chain. + /// + /// The root object. + /// The value to set. + /// + /// If , throws for a null root and for an unsettable final member. + /// If , returns when the chain cannot be navigated or set. + /// + /// if the set succeeded; otherwise . + /// Thrown when is and is . + /// Thrown when the final member is not settable and is . + public bool TrySetValue(TSource? source, TValue value, bool shouldThrow = true) + { + object? current = source; + + if (current is null) + { + if (shouldThrow) + { + throw new ArgumentNullException(nameof(source)); + } + + return false; + } + + for (var i = 0; i < _parentGetters.Length; i++) + { + current = _parentGetters[i](current, _parentArguments[i]); + if (current is null) + { + // Preserve Try* semantics: intermediate nulls soft-fail; do not synthesize exceptions. + return false; + } + } + + if (_setter is null) + { + if (shouldThrow) + { + throw new ArgumentException(_unsettableMemberMessage); + } + + return false; + } + + _setter(current, value, _setterArguments); + return true; + } + } } diff --git a/src/ReactiveUI/Helpers/DoesNotReturnIfAttribute.cs b/src/ReactiveUI/Helpers/DoesNotReturnIfAttribute.cs deleted file mode 100644 index 0f9220da64..0000000000 --- a/src/ReactiveUI/Helpers/DoesNotReturnIfAttribute.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2025 .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 !NET5_0_OR_GREATER - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Indicates that a parameter captures the expression passed for another parameter as a string. -/// -[ExcludeFromCodeCoverage] -[DebuggerNonUserCode] -[AttributeUsage(AttributeTargets.Parameter)] -internal sealed class DoesNotReturnIfAttribute : Attribute -{ - /// Initializes a new instance of the class.. - /// - /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to - /// the associated parameter matches this value. - /// - public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; - - /// Gets a value indicating whether the condition parameter value. - public bool ParameterValue { get; } -} - -#else -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -[assembly: TypeForwardedTo(typeof(DoesNotReturnIfAttribute))] -#endif diff --git a/src/ReactiveUI/Interfaces/ICreatesCommandBinding.cs b/src/ReactiveUI/Interfaces/ICreatesCommandBinding.cs index 882134951b..8c5d044b04 100644 --- a/src/ReactiveUI/Interfaces/ICreatesCommandBinding.cs +++ b/src/ReactiveUI/Interfaces/ICreatesCommandBinding.cs @@ -10,28 +10,10 @@ namespace ReactiveUI; /// /// Classes that implement this interface and registered inside Splat will be /// used to potentially provide binding to a ICommand in the ViewModel to a Control -/// in the View. +/// in the View. This interface is fully AOT-compatible using generic type parameters. /// public interface ICreatesCommandBinding { - /// - /// Returns a positive integer when this class supports - /// BindCommandToObject for this particular Type. If the method - /// isn't supported at all, return a non-positive integer. When multiple - /// implementations return a positive value, the host will use the one - /// which returns the highest value. When in doubt, return '2' or '0'. - /// - /// The type to query for. - /// If true, the host intends to use a custom - /// event target. - /// A positive integer if BCTO is supported, zero or a negative - /// value otherwise. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif - int GetAffinityForObject(Type type, bool hasEventTarget); - /// /// Returns a positive integer when this class supports binding a command /// to an object of the specified type. If the binding is not supported, @@ -42,62 +24,75 @@ public interface ICreatesCommandBinding /// Determines if the host intends to use a custom event target. /// The type of the object to query for compatibility with command binding. /// A positive integer if binding is supported, or zero/a negative value if not supported. -#if NET6_0_OR_GREATER - int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>( - bool hasEventTarget); -#else - int GetAffinityForObject( - bool hasEventTarget); -#endif + int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>(bool hasEventTarget); /// /// Bind an ICommand to a UI object, in the "default" way. The meaning - /// of this is dependent on the implementation. Implement this if you - /// have a new type of UI control that doesn't have + /// of this is dependent on the implementation. This method will discover + /// which event to bind to (e.g., Click, MouseUp) based on the control type. + /// Implement this if you have a new type of UI control that doesn't have /// Command/CommandParameter like WPF or has a non-standard event name /// for "Invoke". /// - /// The command to bind. - /// The target object, usually a UI control of - /// some kind. + /// The type of the target object to which the command is bound. Must be a reference type. + /// The command to bind. Can be null. + /// The target object, usually a UI control of some kind. Can be null. /// An IObservable source whose latest /// value will be passed as the command parameter to the command. Hosts /// will always pass a valid IObservable, but this may be /// Observable.Empty. - /// An IDisposable which will disconnect the binding when - /// disposed. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif - IDisposable? BindCommandToObject(ICommand? command, object? target, IObservable commandParameter); + /// An IDisposable which will disconnect the binding when disposed, or null if no binding was created. + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T>( + ICommand? command, + T? target, + IObservable commandParameter) + where T : class; /// /// Bind an ICommand to a UI object to a specific event. This event may /// be a standard .NET event, or it could be an event derived in another - /// manner (i.e. in MonoTouch). + /// manner (i.e. in MonoTouch). This method is fully AOT-compatible as it + /// uses generic type parameters instead of reflection. /// + /// The type of the target object to which the command is bound. Must be a reference type. /// The event argument type. - /// The command to bind. - /// The target object, usually a UI control of - /// some kind. + /// The command to bind. Can be null. + /// The target object, usually a UI control of some kind. Can be null. /// An IObservable source whose latest - /// value will be passed as the command parameter to the command. Hosts - /// will always pass a valid IObservable, but this may be - /// Observable.Empty. + /// value will be passed as the command parameter to the command. Hosts + /// will always pass a valid IObservable, but this may be + /// Observable.Empty. /// The event to bind to. - /// An IDisposable which will disconnect the binding when disposed. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif - IDisposable BindCommandToObject( + /// An IDisposable which will disconnect the binding when disposed, or null if no binding was created. + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + IDisposable? BindCommandToObject( ICommand? command, - object? target, + T? target, IObservable commandParameter, string eventName) -#if MONO - where TEventArgs : EventArgs -#endif - ; + where T : class; + + /// + /// Binds a command to a specific event on a target object using explicit add/remove handler delegates. + /// + /// The type of the target object. + /// The event arguments type. + /// The command to bind. If , no binding is created. + /// The target object. + /// An observable that supplies command parameter values. + /// Adds the handler to the target event. + /// Removes the handler from the target event. + /// A disposable that unbinds the command. + /// + /// Thrown when , , or is . + /// + IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>( + ICommand? command, + T? target, + IObservable commandParameter, + Action> addHandler, + Action> removeHandler) + where T : class + where TEventArgs : EventArgs; } diff --git a/src/ReactiveUI/Interfaces/ICreatesObservableForProperty.cs b/src/ReactiveUI/Interfaces/ICreatesObservableForProperty.cs index 7e29574a1a..60fdbd2e85 100644 --- a/src/ReactiveUI/Interfaces/ICreatesObservableForProperty.cs +++ b/src/ReactiveUI/Interfaces/ICreatesObservableForProperty.cs @@ -6,51 +6,69 @@ namespace ReactiveUI; /// -/// ICreatesObservableForProperty represents an object that knows how to -/// create notifications for a given type of object. Implement this if you -/// are porting RxUI to a new UI toolkit, or generally want to enable WhenAny -/// for another type of object that can be observed in a unique way. +/// represents a component that can produce change notifications for a +/// given property on a given object. /// +/// +/// Implementations are typically platform-specific (e.g., a UI toolkit) but this interface must remain platform-agnostic. +/// public interface ICreatesObservableForProperty : IEnableLogger { /// - /// Returns a positive integer when this class supports - /// GetNotificationForProperty for this particular Type. If the method - /// isn't supported at all, return a non-positive integer. When multiple - /// implementations return a positive value, the host will use the one - /// which returns the highest value. When in doubt, return '2' or '0'. + /// Returns a positive integer when this instance supports for + /// the specified and . /// - /// The type to query for. - /// The property of the type to query for. - /// If true, returns whether GNFP is supported before a change occurs. - /// A positive integer if GNFP is supported, zero or a negative - /// value otherwise. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif + /// + /// + /// If the method is not supported, return a non-positive integer. + /// When multiple implementations return a positive value, the host selects the highest value. + /// + /// + /// Implementations should avoid expensive work here; this is typically a hot-path query. + /// + /// + /// The runtime type to query. + /// The property name to query. + /// + /// If , indicates the caller requests notifications before the property value changes. + /// If , indicates after-change notifications. + /// + /// + /// A positive integer if supported; zero or a negative value otherwise. + /// + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false); /// - /// Subscribe to notifications on the specified property, given an - /// object and a property name. + /// Subscribes to change notifications for the specified on . /// /// The object to observe. - /// The expression on the object to observe. - /// This will be either a MemberExpression or an IndexExpression - /// depending on the property. + /// + /// The expression describing the observed member. + /// This is typically a MemberExpression or an IndexExpression. + /// + /// The property name to observe. + /// + /// If , signal before the property value changes; otherwise signal after the change. /// - /// The property of the type to query for. - /// If true, signal just before the - /// property value actually changes. If false, signal after the - /// property changes. - /// If true, no warnings should be logged. - /// An IObservable which is signaled whenever the specified - /// property on the object changes. If this cannot be done for a - /// specified value of beforeChanged, return Observable.Never. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] -#endif - IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false); + /// If , warnings should not be logged. + /// + /// An observable that produces an whenever the observed property changes. + /// If observing is not possible for the specified value, implementations should return + /// an observable that never produces values. + /// + /// + /// Thrown when is not compatible with the observing mechanism implemented by the instance. + /// + /// + /// The describes the observed member and is used to populate + /// instances emitted by the returned observable. + /// + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + IObservable> GetNotificationForProperty( + object sender, + Expression expression, + string propertyName, + bool beforeChanged = false, + bool suppressWarnings = false); } diff --git a/src/ReactiveUI/Interfaces/IHandleObservableErrors.cs b/src/ReactiveUI/Interfaces/IHandleObservableErrors.cs index 17da8775a3..867676b9ff 100644 --- a/src/ReactiveUI/Interfaces/IHandleObservableErrors.cs +++ b/src/ReactiveUI/Interfaces/IHandleObservableErrors.cs @@ -14,7 +14,7 @@ namespace ReactiveUI; /// /// /// Normally this IObservable is implemented with a ScheduledSubject whose -/// default Observer is RxApp.DefaultExceptionHandler - this means, that if +/// default Observer is RxSchedulers.DefaultExceptionHandler - this means, that if /// you aren't listening to ThrownExceptions and one appears, the exception /// will appear on the UI thread and crash the application. /// diff --git a/src/ReactiveUI/Interfaces/IPropertyBindingHook.cs b/src/ReactiveUI/Interfaces/IPropertyBindingHook.cs index 8517cbf590..3ad6089850 100644 --- a/src/ReactiveUI/Interfaces/IPropertyBindingHook.cs +++ b/src/ReactiveUI/Interfaces/IPropertyBindingHook.cs @@ -20,10 +20,6 @@ public interface IPropertyBindingHook /// Get current view model properties. /// Get current view properties. /// The Binding direction. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] -#endif bool ExecuteHook( object? source, object target, diff --git a/src/ReactiveUI/Interfaces/IReactiveNotifyPropertyChanged.cs b/src/ReactiveUI/Interfaces/IReactiveNotifyPropertyChanged.cs index 7113263b87..b393c3d29a 100644 --- a/src/ReactiveUI/Interfaces/IReactiveNotifyPropertyChanged.cs +++ b/src/ReactiveUI/Interfaces/IReactiveNotifyPropertyChanged.cs @@ -33,9 +33,5 @@ public interface IReactiveNotifyPropertyChanged /// /// An object that, when disposed, reenables change /// notifications. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SuppressChangeNotifications uses extension methods that require dynamic code generation")] - [RequiresUnreferencedCode("SuppressChangeNotifications uses extension methods that may require unreferenced code")] -#endif IDisposable SuppressChangeNotifications(); } diff --git a/src/ReactiveUI/Interfaces/IRegistrar.cs b/src/ReactiveUI/Interfaces/IRegistrar.cs new file mode 100644 index 0000000000..55860fb1fb --- /dev/null +++ b/src/ReactiveUI/Interfaces/IRegistrar.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2025 .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; + +/// +/// Interface for registering services in a dependency injection container in an AOT-friendly manner. +/// This interface provides generic registration methods that preserve type information at compile time, +/// avoiding the need for runtime Type reflection and DynamicallyAccessedMembers attributes. +/// +public interface IRegistrar +{ + /// + /// Registers a constant value for a service type. The factory function is called once + /// and the result is registered as a singleton. + /// + /// The service type to register. + /// A factory function that creates the service instance. + /// An optional contract name for multiple registrations of the same type. + void RegisterConstant(Func factory, string? contract = null) + where TService : class; + + /// + /// Registers a lazy singleton for a service type. The factory function is called + /// the first time the service is resolved, and the same instance is returned for all subsequent resolutions. + /// + /// The service type to register. + /// A factory function that creates the service instance. + /// An optional contract name for multiple registrations of the same type. + void RegisterLazySingleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TService>(Func factory, string? contract = null) + where TService : class; + + /// + /// Registers a factory for a service type. The factory function is called each time + /// the service is resolved, creating a new instance. + /// + /// The service type to register. + /// A factory function that creates the service instance. + /// An optional contract name for multiple registrations of the same type. + void Register(Func factory, string? contract = null) + where TService : class; +} diff --git a/src/ReactiveUI/Interfaces/ISuspensionDriver.cs b/src/ReactiveUI/Interfaces/ISuspensionDriver.cs index 4bbf0d3ee4..889d5fe487 100644 --- a/src/ReactiveUI/Interfaces/ISuspensionDriver.cs +++ b/src/ReactiveUI/Interfaces/ISuspensionDriver.cs @@ -3,43 +3,99 @@ // 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.Text.Json.Serialization.Metadata; + namespace ReactiveUI; /// -/// ISuspensionDriver represents a class that can load/save state to persistent -/// storage. Most platforms have a basic implementation of this class, but you -/// probably want to write your own. +/// Represents a driver capable of loading and saving application state +/// to persistent storage. /// +/// +/// +/// This interface supports both legacy reflection-based serialization +/// and trimming/AOT-safe serialization using System.Text.Json source generation. +/// +/// +/// Implementations that support trimming or AOT should prefer the overloads +/// that accept . +/// +/// public interface ISuspensionDriver { /// /// Loads the application state from persistent storage. /// - /// An object observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("LoadState implementations may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("LoadState implementations may use serialization which may require unreferenced code")] -#endif + /// + /// An observable that produces the deserialized application state + /// (or ). + /// + /// + /// This member typically relies on reflection-based serialization and is not + /// trimming or AOT friendly. + /// + [RequiresUnreferencedCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer LoadState(JsonTypeInfo) for trimming or AOT scenarios.")] + [RequiresDynamicCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer LoadState(JsonTypeInfo) for trimming or AOT scenarios.")] IObservable LoadState(); /// - /// Saves the application state to disk. + /// Saves the application state to persistent storage. + /// + /// The type of the application state. + /// The application state to persist. + /// + /// An observable that completes when the state has been saved. + /// + /// + /// This member typically relies on reflection-based serialization and is not + /// trimming or AOT friendly. + /// + [RequiresUnreferencedCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer SaveState(T, JsonTypeInfo) for trimming or AOT scenarios.")] + [RequiresDynamicCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer SaveState(T, JsonTypeInfo) for trimming or AOT scenarios.")] + IObservable SaveState(T state); + + /// + /// Loads application state from persistent storage using + /// source-generated System.Text.Json metadata. + /// + /// The expected state type. + /// + /// The source-generated metadata for . + /// + /// + /// An observable that produces the deserialized state + /// (or ). + /// + IObservable LoadState(JsonTypeInfo typeInfo); + + /// + /// Saves application state to persistent storage using + /// source-generated System.Text.Json metadata. /// - /// The application state. - /// A completed observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SaveState implementations may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("SaveState implementations may use serialization which may require unreferenced code")] -#endif - IObservable SaveState(object state); + /// The state type. + /// The state to persist. + /// + /// The source-generated metadata for . + /// + /// + /// An observable that completes when persistence succeeds. + /// + IObservable SaveState(T state, JsonTypeInfo typeInfo); /// - /// Invalidates the application state (i.e. deletes it from disk). + /// Invalidates the persisted application state + /// (for example, by deleting it from disk). /// - /// A completed observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("InvalidateState uses JsonSerializer which requires dynamic code generation")] - [RequiresUnreferencedCode("InvalidateState uses JsonSerializer which may require unreferenced code")] -#endif + /// + /// An observable that completes when the state has been invalidated. + /// IObservable InvalidateState(); } diff --git a/src/ReactiveUI/Interfaces/ISuspensionHost.cs b/src/ReactiveUI/Interfaces/ISuspensionHost.cs index 06343db148..50c24d437c 100644 --- a/src/ReactiveUI/Interfaces/ISuspensionHost.cs +++ b/src/ReactiveUI/Interfaces/ISuspensionHost.cs @@ -25,7 +25,7 @@ namespace ReactiveUI; /// /// These observables abstract platform terms such as "Launching", "Activated", and "Closing" into a /// consistent API so shared code can persist state without branching on specific UI stacks. Most -/// applications call RxApp.SuspensionHost.SetupDefaultSuspendResume() during startup to wire +/// applications call RxSuspension.SuspensionHost.SetupDefaultSuspendResume() during startup to wire /// default handlers, but the properties are public so advanced hosts can plug in their own monitoring. /// /// @@ -37,7 +37,7 @@ namespace ReactiveUI; /// /// /// new ShellState(); /// /// suspensionHost.IsLaunchingNew.Subscribe(_ => diff --git a/src/ReactiveUI/Interfaces/ISuspensionHost{TAppState}.cs b/src/ReactiveUI/Interfaces/ISuspensionHost{TAppState}.cs new file mode 100644 index 0000000000..a1dd922d8d --- /dev/null +++ b/src/ReactiveUI/Interfaces/ISuspensionHost{TAppState}.cs @@ -0,0 +1,58 @@ +// Copyright (c) 2025 .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.Interfaces; + +/// +/// Represents a standardized version of host operating system lifecycle signals with a strongly-typed application state. +/// +/// The application state type. +/// +/// +/// This interface is a strongly-typed companion to . It remains platform-agnostic and +/// retains the same lifecycle observables, while providing a typed surface. +/// +/// +/// Compatibility: this interface derives from . Implementations should typically +/// implement explicitly to project the typed state through the legacy object-based +/// contract. +/// +/// +public interface ISuspensionHost : ISuspensionHost +{ + /// + /// Gets or sets a function that can be used to create a new application state instance. + /// + /// + /// This is the typed counterpart to and is typically used when + /// the application is launching fresh or recovering from an invalidated state. + /// + Func? CreateNewAppStateTyped { get; set; } + + /// + /// Gets or sets the current application state. + /// + /// + /// This is the typed counterpart to . Implementations should ensure that + /// the legacy view remains consistent with this property. + /// + TAppState? AppStateValue { get; set; } + + /// + /// Gets an observable that signals when is assigned. + /// + /// + /// + /// This is a trimming/AOT-friendly change signal for app state updates. + /// Consumers can use this to observe state transitions without relying on ReactiveUI + /// property-change expression pipelines. + /// + /// + /// The observable does not guarantee replay; consumers that need the current value should combine this with + /// (or use an extension that emits the current value first). + /// + /// + IObservable AppStateValueChanged { get; } +} diff --git a/src/ReactiveUI/Interfaces/IViewLocator.cs b/src/ReactiveUI/Interfaces/IViewLocator.cs index 1aa8922990..f15a6c68b2 100644 --- a/src/ReactiveUI/Interfaces/IViewLocator.cs +++ b/src/ReactiveUI/Interfaces/IViewLocator.cs @@ -18,18 +18,23 @@ namespace ReactiveUI; /// /// /// (T? viewModel, string? contract = null) +/// private readonly Dictionary> _mappings = new(); +/// +/// public void Register(Func factory) +/// where TViewModel : class +/// where TView : class, IViewFor /// { -/// if (viewModel is null) -/// { -/// return null; -/// } +/// _mappings[typeof(TViewModel)] = () => factory(); +/// } /// -/// var viewTypeName = viewModel.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); -/// var viewType = Type.GetType(viewTypeName); -/// return viewType is null ? null : (IViewFor?)Activator.CreateInstance(viewType); +/// public IViewFor? ResolveView(string? contract = null) +/// where TViewModel : class +/// { +/// return _mappings.TryGetValue(typeof(TViewModel), out var factory) +/// ? (IViewFor)factory() +/// : null; /// } /// } /// ]]> @@ -38,15 +43,21 @@ namespace ReactiveUI; public interface IViewLocator : IEnableLogger { /// - /// Determines the view for an associated view model. + /// Resolves a view for a view model type known at compile time. Fully AOT-compatible. + /// + /// The view model type to resolve a view for. + /// Optional contract allowing multiple view registrations per view model. + /// The resolved view or when no registration is available. + IViewFor? ResolveView(string? contract = null) + where TViewModel : class; + + /// + /// Resolves a view for a view model instance using runtime type information. /// - /// The view model type. - /// The view model for which a view is required. + /// The view model instance to resolve a view for. /// Optional contract allowing multiple view registrations per view model. /// The resolved view or when no registration is available. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ResolveView uses reflection and type discovery which require dynamic code generation")] - [RequiresUnreferencedCode("ResolveView uses reflection and type discovery which may require unreferenced code")] -#endif - IViewFor? ResolveView(T? viewModel, string? contract = null); + [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.")] + IViewFor? ResolveView(object? instance, string? contract = null); } diff --git a/src/ReactiveUI/Interfaces/IViewModule.cs b/src/ReactiveUI/Interfaces/IViewModule.cs new file mode 100644 index 0000000000..756c609b9b --- /dev/null +++ b/src/ReactiveUI/Interfaces/IViewModule.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2025 .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; + +/// +/// Represents a module that registers view-to-viewmodel mappings for AOT-compatible view resolution. +/// +/// +/// +/// View modules provide a way to organize view registrations by feature or module. +/// Implement this interface to create reusable, testable view registration logic. +/// +/// +/// +/// +/// (() => new LoginView()) +/// .Map(() => new RegisterView()) +/// .Map(() => new ForgotPasswordView()); +/// } +/// } +/// ]]> +/// +/// +public interface IViewModule +{ + /// + /// Registers view-to-viewmodel mappings with the provided view locator. + /// + /// The view locator to register mappings with. + void RegisterViews(DefaultViewLocator locator); +} diff --git a/src/ReactiveUI/Interfaces/IWantsToRegisterStuff.cs b/src/ReactiveUI/Interfaces/IWantsToRegisterStuff.cs index ae7691cd2d..745678ecc2 100644 --- a/src/ReactiveUI/Interfaces/IWantsToRegisterStuff.cs +++ b/src/ReactiveUI/Interfaces/IWantsToRegisterStuff.cs @@ -6,19 +6,42 @@ namespace ReactiveUI; /// -/// Used by ReactiveUI when first starting up, it will seek out classes -/// inside our own ReactiveUI projects. The implemented methods will -/// register with Splat their dependencies. +/// Represents a class that can register services with ReactiveUI's dependency resolver. /// +/// +/// +/// This interface is used with the ReactiveUI builder pattern to provide custom registrations. +/// The registration methods use generic types to avoid runtime reflection, making them compatible +/// with AOT compilation and trimming. +/// +/// +/// Usage with builder: +/// +/// public class MyCustomRegistrations : IWantsToRegisterStuff +/// { +/// public void Register(IRegistrar registrar) +/// { +/// registrar.RegisterConstant<IMyService>(() => new MyService()); +/// registrar.RegisterLazySingleton<IMyViewModel>(() => new MyViewModel()); +/// } +/// } +/// +/// // In your app initialization: +/// RxAppBuilder.CreateReactiveUIBuilder() +/// .WithCoreServices() +/// .WithPlatformServices() +/// .WithRegistration(new MyCustomRegistrations()) +/// .BuildApp(); +/// +/// +/// public interface IWantsToRegisterStuff { /// - /// Register platform dependencies inside Splat. + /// Register platform dependencies using the provided registrar. + /// This method uses generic registration to avoid runtime Type reflection, + /// making it compatible with AOT compilation and trimming. /// - /// A method the deriving class will class to register the type. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Uses reflection to create instances of types.")] - [RequiresDynamicCode("Uses reflection to create instances of types.")] -#endif - void Register(Action, Type> registerFunction); + /// The AOT-friendly registrar to use for registering services. + void Register(IRegistrar registrar); } diff --git a/src/ReactiveUI/Legacy/CollectionDebugView.cs b/src/ReactiveUI/Legacy/CollectionDebugView.cs deleted file mode 100644 index 570f5fbcbc..0000000000 --- a/src/ReactiveUI/Legacy/CollectionDebugView.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2025 .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; - -namespace ReactiveUI.Legacy; - -[ExcludeFromCodeCoverage] -internal sealed class CollectionDebugView(ICollection collection) -{ - private readonly ICollection _collection = collection ?? throw new ArgumentNullException(nameof(collection), "collection is null."); - - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public T[] Items - { - get - { - var array = new T[_collection.Count]; - _collection.CopyTo(array, 0); - return array; - } - } -} diff --git a/src/ReactiveUI/Legacy/RefcountDisposeWrapper.cs b/src/ReactiveUI/Legacy/RefcountDisposeWrapper.cs deleted file mode 100644 index 5e8bf007ad..0000000000 --- a/src/ReactiveUI/Legacy/RefcountDisposeWrapper.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2025 .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.Legacy; - -[ExcludeFromCodeCoverage] -internal sealed class RefcountDisposeWrapper(IDisposable inner) -{ - private IDisposable? _inner = inner; - private int _refCount = 1; - - public void AddRef() => Interlocked.Increment(ref _refCount); - - public void Release() - { - if (Interlocked.Decrement(ref _refCount) == 0) - { - var inner = Interlocked.Exchange(ref _inner, null); - inner?.Dispose(); - } - } -} diff --git a/src/ReactiveUI/Mixins/AutoPersistHelper.cs b/src/ReactiveUI/Mixins/AutoPersistHelper.cs index fdeb7a31a7..0b5fc04d57 100644 --- a/src/ReactiveUI/Mixins/AutoPersistHelper.cs +++ b/src/ReactiveUI/Mixins/AutoPersistHelper.cs @@ -5,30 +5,40 @@ using System.Collections.Specialized; using System.Reflection; +using System.Runtime.CompilerServices; using DynamicData; using DynamicData.Binding; +using ReactiveUI.Builder; + namespace ReactiveUI; /// /// Helper extension method class associated with the AutoPersist related functionality. /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] -[RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static class AutoPersistHelper { - private static readonly MemoizingMRUCache> _persistablePropertiesCache = new( - static (type, _) => type.GetTypeInfo().DeclaredProperties - .Where(static x => x.CustomAttributes.Any(static y => typeof(DataMemberAttribute).GetTypeInfo().IsAssignableFrom(y.AttributeType.GetTypeInfo()))) - .ToDictionary(static k => k.Name, static _ => true), - RxApp.SmallCacheLimit); + /// + /// Stores per-runtime-type persistence metadata computed via reflection. + /// + /// + /// + /// This cache is intentionally non-evicting for correctness and predictability. The number of distinct reactive + /// object runtime types in a typical application is small and stable; MRU eviction introduces churn and can + /// re-trigger expensive reflection. + /// + /// + /// This cache is used only when callers use the legacy reflection-based overloads and + /// the generic type does not match the runtime type of the instance. + /// + /// + private static readonly ConditionalWeakTable PersistMetadataByType = new(); - private static readonly MemoizingMRUCache _dataContractCheckCache = new( - static (t, _) => t.GetTypeInfo().GetCustomAttributes(typeof(DataContractAttribute), true).Length > 0, - RxApp.SmallCacheLimit); + /// + /// Initializes static members of the class. + /// + static AutoPersistHelper() => RxAppBuilder.EnsureInitialized(); /// /// AutoPersist allows you to automatically call a method when an object @@ -38,20 +48,36 @@ public static class AutoPersistHelper /// object to be saved. /// /// The reactive object type. - /// - /// The reactive object to watch for changes. - /// - /// - /// The asynchronous method to call to save the object to disk. - /// + /// The reactive object to watch for changes. + /// The asynchronous method to call to save the object to disk. /// /// The interval to save the object on. Note that if an object is constantly changing, /// it is possible that it will never be saved. /// - /// A Disposable to disable automatic persistence. - public static IDisposable AutoPersist(this T @this, Func> doPersist, TimeSpan? interval = null) - where T : IReactiveObject => - @this.AutoPersist(doPersist, Observable.Never, interval); + /// A disposable to disable automatic persistence. + /// + /// + /// This overload preserves historical behavior by reflecting over the runtime type when it differs from + /// . This behavior is trimming/AOT-unsafe unless the application explicitly preserves the + /// required members and attribute metadata. + /// + /// + /// For trimming/AOT-friendly behavior, prefer the overloads that accept . + /// + /// + [RequiresUnreferencedCode( + "AutoPersist may reflect over the runtime type when it differs from T. In trimmed/AOT builds, required property/attribute metadata " + + "may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runtime reflection.")] + [RequiresDynamicCode( + "AutoPersist may reflect over the runtime type when it differs from T. In trimmed/AOT builds, required property/attribute metadata " + + "may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runtime reflection.")] + public static IDisposable AutoPersist< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>( + this T @this, + Func> doPersist, + TimeSpan? interval = null) + where T : IReactiveObject + => @this.AutoPersist(doPersist, Observable.Never, interval); /// /// AutoPersist allows you to automatically call a method when an object @@ -62,12 +88,8 @@ public static IDisposable AutoPersist(this T @this, Func /// /// The reactive object type. /// The save signal type. - /// - /// The reactive object to watch for changes. - /// - /// - /// The asynchronous method to call to save the object to disk. - /// + /// The reactive object to watch for changes. + /// The asynchronous method to call to save the object to disk. /// /// When invoked, the object will be saved regardless of whether it has changed. /// @@ -75,30 +97,69 @@ public static IDisposable AutoPersist(this T @this, Func /// The interval to save the object on. Note that if an object is constantly changing, /// it is possible that it will never be saved. /// - /// A Disposable to disable automatic persistence. - public static IDisposable AutoPersist(this T @this, Func> doPersist, IObservable manualSaveSignal, TimeSpan? interval = null) - where T : IReactiveObject + /// A disposable to disable automatic persistence. + /// Thrown when the object is not annotated with [DataContract]. + /// + /// + /// This overload preserves historical behavior by reflecting over the runtime type when it differs from + /// . This behavior is trimming/AOT-unsafe unless the application explicitly preserves the + /// required members and attribute metadata. + /// + /// + /// For trimming/AOT-friendly behavior, prefer the overloads that accept . + /// + /// + [RequiresUnreferencedCode( + "AutoPersist may reflect over the runtime type when it differs from T. In trimmed/AOT builds, required property/attribute metadata " + + "may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runtime reflection.")] + [RequiresDynamicCode( + "AutoPersist may reflect over the runtime type when it differs from T. In trimmed/AOT builds, required property/attribute metadata " + + "may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runtime reflection.")] + public static IDisposable AutoPersist< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, + TDontCare>( + this T @this, + Func> doPersist, + IObservable manualSaveSignal, + TimeSpan? interval = null) + where T : IReactiveObject { + ArgumentExceptionHelper.ThrowIfNull(@this); + ArgumentExceptionHelper.ThrowIfNull(doPersist); + ArgumentExceptionHelper.ThrowIfNull(manualSaveSignal); + interval ??= TimeSpan.FromSeconds(3.0); - if (!_dataContractCheckCache.Get(@this.GetType())) + // Fast path: if T is the actual runtime type, use per-closed-generic cache (no CWT lookup). + // Slow path: preserve historical semantics by reflecting over the runtime type. + var runtimeType = @this.GetType(); + var metadata = runtimeType == typeof(T) + ? PersistMetadataHolder.Metadata + : GetMetadataForUnknownRuntimeType(runtimeType); + + if (!metadata.HasDataContract) { throw new ArgumentException("AutoPersist can only be applied to objects with [DataContract]"); } - var persistableProperties = _persistablePropertiesCache.Get(@this.GetType()); + var persistablePropertyNames = metadata.PersistablePropertyNames; - var saveHint = @this.GetChangedObservable().Where(x => x.PropertyName is not null && persistableProperties.ContainsKey(x.PropertyName)).Select(_ => Unit.Default).Merge(manualSaveSignal.Select(_ => Unit.Default)); + var saveHint = + @this.GetChangedObservable() + .Where(x => x.PropertyName is not null && persistablePropertyNames.Contains(x.PropertyName)) + .Select(static _ => Unit.Default) + .Merge(manualSaveSignal.Select(static _ => Unit.Default)); - var autoSaver = saveHint - .Throttle(interval.Value, RxApp.TaskpoolScheduler) - .SelectMany(_ => doPersist(@this)) - .Publish(); + var autoSaver = + saveHint + .Throttle(interval.Value, RxSchedulers.TaskpoolScheduler) + .SelectMany(_ => doPersist(@this)) + .Publish(); // NB: This rigamarole is to prevent the initialization of a class - // from triggering a save + // from triggering a save. var ret = new SingleAssignmentDisposable(); - RxApp.MainThreadScheduler.Schedule(() => + RxSchedulers.MainThreadScheduler.Schedule(() => { if (ret.IsDisposed) { @@ -112,24 +173,364 @@ public static IDisposable AutoPersist(this T @this, Func - /// Apply AutoPersistence to all objects in a collection. Items that are - /// no longer in the collection won't be persisted anymore. + /// Apply AutoPersistence to all objects in a collection using explicit persistence metadata. + /// This overload performs no runtime reflection and is suitable for trimming/AOT scenarios. + /// + /// The item type. + /// The reactive collection to watch for changes. + /// The asynchronous method to call to save the object to disk. + /// The persistence metadata that determines which properties trigger persistence. + /// + /// The interval to save the object on. Note that if an object is constantly changing, + /// it is possible that it will never be saved. + /// + /// A disposable to disable automatic persistence. + /// + /// Thrown when indicates the object is not annotated with [DataContract]. + /// + public static IDisposable AutoPersistCollection( + this ObservableCollection @this, + Func> doPersist, + AutoPersistMetadata metadata, + TimeSpan? interval = null) + where TItem : IReactiveObject + => AutoPersistCollection(@this, doPersist, Observable.Never, metadata, interval); + + /// + /// Apply AutoPersistence to all objects in a collection using explicit persistence metadata. + /// This overload performs no runtime reflection and is suitable for trimming/AOT scenarios. + /// + /// The item type. + /// The manual save signal type. + /// The reactive collection to watch for changes. + /// The asynchronous method to call to save the object to disk. + /// When invoked, the object will be saved regardless of whether it has changed. + /// The persistence metadata that determines which properties trigger persistence. + /// The interval to save the object on. + /// A disposable to disable automatic persistence. + /// + /// Thrown when indicates the object is not annotated with [DataContract]. + /// + public static IDisposable AutoPersistCollection( + this ObservableCollection @this, + Func> doPersist, + IObservable manualSaveSignal, + AutoPersistMetadata metadata, + TimeSpan? interval = null) + where TItem : IReactiveObject + => AutoPersistCollection, TDontCare>(@this, doPersist, manualSaveSignal, metadata, interval); + + /// + /// Apply AutoPersistence to all objects in a collection using explicit persistence metadata. + /// This overload performs no runtime reflection and is suitable for trimming/AOT scenarios. + /// + /// The item type. + /// The collection type. + /// The manual save signal type. + /// The reactive collection to watch for changes. + /// The asynchronous method to call to save the object to disk. + /// When invoked, the object will be saved regardless of whether it has changed. + /// The persistence metadata that determines which properties trigger persistence. + /// The interval to save the object on. + /// A disposable to disable automatic persistence. + /// + /// Thrown when indicates the object is not annotated with [DataContract]. + /// + public static IDisposable AutoPersistCollection( + this TCollection @this, + Func> doPersist, + IObservable manualSaveSignal, + AutoPersistMetadata metadata, + TimeSpan? interval = null) + where TItem : IReactiveObject + where TCollection : INotifyCollectionChanged, IEnumerable + { + ArgumentExceptionHelper.ThrowIfNull(@this); + ArgumentExceptionHelper.ThrowIfNull(doPersist); + ArgumentExceptionHelper.ThrowIfNull(manualSaveSignal); + ArgumentExceptionHelper.ThrowIfNull(metadata); + + if (!metadata.HasDataContract) + { + throw new ArgumentException("AutoPersist can only be applied to objects with [DataContract]", nameof(metadata)); + } + + var disposerList = new Dictionary(); + + var subscription = @this.ActOnEveryObject( + onAdd: x => + { + if (disposerList.ContainsKey(x)) + { + return; + } + + disposerList[x] = x.AutoPersist(doPersist, manualSaveSignal, metadata, interval); + }, + onRemove: x => + { + if (!disposerList.TryGetValue(x, out var d)) + { + return; + } + + d.Dispose(); + disposerList.Remove(x); + }); + + return Disposable.Create(() => + { + subscription.Dispose(); + + foreach (var kvp in disposerList) + { + kvp.Value.Dispose(); + } + + disposerList.Clear(); + }); + } + + /// + /// Apply AutoPersistence to all objects in a read-only collection using explicit persistence metadata. + /// This overload performs no runtime reflection and is suitable for trimming/AOT scenarios. /// /// The item type. - /// - /// The reactive collection to watch for changes. + /// The manual save signal type. + /// The reactive collection to watch for changes. + /// The asynchronous method to call to save the object to disk. + /// When invoked, the object will be saved regardless of whether it has changed. + /// The persistence metadata that determines which properties trigger persistence. + /// The interval to save the object on. + /// A disposable to disable automatic persistence. + /// + /// Thrown when indicates the object is not annotated with [DataContract]. + /// + public static IDisposable AutoPersistCollection( + this ReadOnlyObservableCollection @this, + Func> doPersist, + IObservable manualSaveSignal, + AutoPersistMetadata metadata, + TimeSpan? interval = null) + where TItem : IReactiveObject + => AutoPersistCollection, TDontCare>(@this, doPersist, manualSaveSignal, metadata, interval); + + /// + /// Apply AutoPersistence to all objects in a collection using a metadata provider. + /// This overload performs no runtime reflection and is suitable for trimming/AOT scenarios, + /// including polymorphic collections. + /// + /// The item type. + /// The collection type. + /// The manual save signal type. + /// The reactive collection to watch for changes. + /// The asynchronous method to call to save the object to disk. + /// When invoked, the object will be saved regardless of whether it has changed. + /// + /// A function that returns the persistence metadata to use for the specific item instance. + /// + /// The interval to save the object on. + /// A disposable to disable automatic persistence. + /// + /// Thrown when is . + /// + public static IDisposable AutoPersistCollection( + this TCollection @this, + Func> doPersist, + IObservable manualSaveSignal, + Func metadataProvider, + TimeSpan? interval = null) + where TItem : IReactiveObject + where TCollection : INotifyCollectionChanged, IEnumerable + { + ArgumentExceptionHelper.ThrowIfNull(@this); + ArgumentExceptionHelper.ThrowIfNull(doPersist); + ArgumentExceptionHelper.ThrowIfNull(manualSaveSignal); + ArgumentExceptionHelper.ThrowIfNull(metadataProvider); + + var disposerList = new Dictionary(); + + var subscription = @this.ActOnEveryObject( + onAdd: x => + { + if (disposerList.ContainsKey(x)) + { + return; + } + + // Non-RUC path: caller provides metadata explicitly. + var metadata = metadataProvider(x); + disposerList[x] = x.AutoPersist(doPersist, manualSaveSignal, metadata, interval); + }, + onRemove: x => + { + if (!disposerList.TryGetValue(x, out var d)) + { + return; + } + + d.Dispose(); + disposerList.Remove(x); + }); + + return Disposable.Create(() => + { + subscription.Dispose(); + + foreach (var kvp in disposerList) + { + kvp.Value.Dispose(); + } + + disposerList.Clear(); + }); + } + + /// + /// Creates a metadata provider for homogeneous collections where is the concrete runtime type. + /// This helper performs no runtime reflection and is suitable for trimming/AOT scenarios. + /// + /// The item type. + /// A function returning metadata for . + public static Func CreateMetadataProvider< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] TItem>() + where TItem : IReactiveObject + { + var metadata = CreateMetadata(); + return _ => metadata; + } + + /// + /// AutoPersist overload that performs no runtime reflection and is suitable for trimming/AOT scenarios. + /// + /// The reactive object type. + /// The reactive object to watch for changes. + /// The asynchronous method to call to save the object to disk. + /// The persistence metadata to use for determining persistable properties. + /// The interval to save the object on. + /// A disposable to disable automatic persistence. + /// Thrown when indicates the object is not persistable. + public static IDisposable AutoPersist( + this T @this, + Func> doPersist, + AutoPersistMetadata metadata, + TimeSpan? interval = null) + where T : IReactiveObject + => @this.AutoPersist(doPersist, Observable.Never, metadata, interval); + + /// + /// AutoPersist overload that performs no runtime reflection and is suitable for trimming/AOT scenarios. + /// + /// The reactive object type. + /// The save signal type. + /// The reactive object to watch for changes. + /// The asynchronous method to call to save the object to disk. + /// + /// When invoked, the object will be saved regardless of whether it has changed. /// - /// - /// The asynchronous method to call to save the object to disk. + /// The persistence metadata to use for determining persistable properties. + /// + /// The interval to save the object on. Note that if an object is constantly changing, + /// it is possible that it will never be saved. /// + /// A disposable to disable automatic persistence. + /// Thrown when indicates the object is not persistable. + public static IDisposable AutoPersist( + this T @this, + Func> doPersist, + IObservable manualSaveSignal, + AutoPersistMetadata metadata, + TimeSpan? interval = null) + where T : IReactiveObject + { + ArgumentExceptionHelper.ThrowIfNull(@this); + ArgumentExceptionHelper.ThrowIfNull(doPersist); + ArgumentExceptionHelper.ThrowIfNull(manualSaveSignal); + ArgumentExceptionHelper.ThrowIfNull(metadata); + + if (!metadata.HasDataContract) + { + throw new ArgumentException("AutoPersist can only be applied to objects with [DataContract]", nameof(metadata)); + } + + interval ??= TimeSpan.FromSeconds(3.0); + + var persistablePropertyNames = metadata.PersistablePropertyNames; + + var saveHint = + @this.GetChangedObservable() + .Where(x => x.PropertyName is not null && persistablePropertyNames.Contains(x.PropertyName)) + .Select(static _ => Unit.Default) + .Merge(manualSaveSignal.Select(static _ => Unit.Default)); + + var autoSaver = + saveHint + .Throttle(interval.Value, RxSchedulers.TaskpoolScheduler) + .SelectMany(_ => doPersist(@this)) + .Publish(); + + var ret = new SingleAssignmentDisposable(); + RxSchedulers.MainThreadScheduler.Schedule(() => + { + if (ret.IsDisposed) + { + return; + } + + ret.Disposable = autoSaver.Connect(); + }); + + return ret; + } + + /// + /// Creates trimming/AOT-friendly persistence metadata for . + /// + /// + /// The type to analyze for [DataContract] and [DataMember]. + /// + /// The computed persistence metadata. + /// + /// This method is analyzable by the trimmer due to the + /// on + /// and uses no runtime type discovery. + /// + public static AutoPersistMetadata CreateMetadata< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>() + where T : IReactiveObject + => PersistMetadataHolder.Metadata.Public; + + /// + /// Apply AutoPersistence to all objects in a collection. Items that are + /// no longer in the collection won't be persisted anymore. + /// + /// The item type. + /// The reactive collection to watch for changes. + /// The asynchronous method to call to save the object to disk. /// /// The interval to save the object on. Note that if an object is constantly changing, /// it is possible that it will never be saved. /// - /// A Disposable to disable automatic persistence. - public static IDisposable AutoPersistCollection(this ObservableCollection @this, Func> doPersist, TimeSpan? interval = null) // TODO: Create Test - where TItem : IReactiveObject => - AutoPersistCollection(@this, doPersist, Observable.Never, interval); + /// A disposable to disable automatic persistence. + /// + /// This overload preserves historical behavior by delegating to the reflection-based AutoPersist pipeline for each item. + /// In trimming/AOT scenarios, required property/attribute metadata may be removed unless explicitly preserved. + /// Prefer the overloads that accept (or a metadata provider) to avoid runtime reflection. + /// + [RequiresUnreferencedCode( + "AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. " + + "In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. " + + "Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [RequiresDynamicCode( + "AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. " + + "In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. " + + "Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + public static IDisposable AutoPersistCollection( + this ObservableCollection @this, + Func> doPersist, + TimeSpan? interval = null) + where TItem : IReactiveObject + => AutoPersistCollection(@this, doPersist, Observable.Never, interval); /// /// Apply AutoPersistence to all objects in a collection. Items that are @@ -137,12 +538,8 @@ public static IDisposable AutoPersistCollection(this ObservableCollection /// /// The item type. /// The return signal type. - /// - /// The reactive collection to watch for changes. - /// - /// - /// The asynchronous method to call to save the object to disk. - /// + /// The reactive collection to watch for changes. + /// The asynchronous method to call to save the object to disk. /// /// When invoked, the object will be saved regardless of whether it has changed. /// @@ -150,10 +547,27 @@ public static IDisposable AutoPersistCollection(this ObservableCollection /// The interval to save the object on. Note that if an object is constantly changing, /// it is possible that it will never be saved. /// - /// A Disposable to disable automatic persistence. - public static IDisposable AutoPersistCollection(this ObservableCollection @this, Func> doPersist, IObservable manualSaveSignal, TimeSpan? interval = null) - where TItem : IReactiveObject => - AutoPersistCollection, TDontCare>(@this, doPersist, manualSaveSignal, interval); + /// A disposable to disable automatic persistence. + /// + /// This overload preserves historical behavior by delegating to the reflection-based AutoPersist pipeline for each item. + /// In trimming/AOT scenarios, required property/attribute metadata may be removed unless explicitly preserved. + /// Prefer the overloads that accept (or a metadata provider) to avoid runtime reflection. + /// + [RequiresUnreferencedCode( + "AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. " + + "In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. " + + "Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [RequiresDynamicCode( + "AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. " + + "In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. " + + "Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + public static IDisposable AutoPersistCollection( + this ObservableCollection @this, + Func> doPersist, + IObservable manualSaveSignal, + TimeSpan? interval = null) + where TItem : IReactiveObject + => AutoPersistCollection, TDontCare>(@this, doPersist, manualSaveSignal, interval); /// /// Apply AutoPersistence to all objects in a collection. Items that are @@ -161,12 +575,8 @@ public static IDisposable AutoPersistCollection(this Observabl /// /// The item type. /// The signal type. - /// - /// The reactive collection to watch for changes. - /// - /// - /// The asynchronous method to call to save the object to disk. - /// + /// The reactive collection to watch for changes. + /// The asynchronous method to call to save the object to disk. /// /// When invoked, the object will be saved regardless of whether it has changed. /// @@ -174,10 +584,27 @@ public static IDisposable AutoPersistCollection(this Observabl /// The interval to save the object on. Note that if an object is constantly changing, /// it is possible that it will never be saved. /// - /// A Disposable to disable automatic persistence. - public static IDisposable AutoPersistCollection(this ReadOnlyObservableCollection @this, Func> doPersist, IObservable manualSaveSignal, TimeSpan? interval = null) // TODO: Create Test - where TItem : IReactiveObject => - AutoPersistCollection, TDontCare>(@this, doPersist, manualSaveSignal, interval); + /// A disposable to disable automatic persistence. + /// + /// This overload preserves historical behavior by delegating to the reflection-based AutoPersist pipeline for each item. + /// In trimming/AOT scenarios, required property/attribute metadata may be removed unless explicitly preserved. + /// Prefer the overloads that accept (or a metadata provider) to avoid runtime reflection. + /// + [RequiresUnreferencedCode( + "AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. " + + "In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. " + + "Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [RequiresDynamicCode( + "AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. " + + "In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. " + + "Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + public static IDisposable AutoPersistCollection( + this ReadOnlyObservableCollection @this, + Func> doPersist, + IObservable manualSaveSignal, + TimeSpan? interval = null) + where TItem : IReactiveObject + => AutoPersistCollection, TDontCare>(@this, doPersist, manualSaveSignal, interval); /// /// Apply AutoPersistence to all objects in a collection. Items that are @@ -186,12 +613,8 @@ public static IDisposable AutoPersistCollection(this ReadOnlyO /// The item type. /// The collection type. /// The signal type. - /// - /// The reactive collection to watch for changes. - /// - /// - /// The asynchronous method to call to save the object to disk. - /// + /// The reactive collection to watch for changes. + /// The asynchronous method to call to save the object to disk. /// /// When invoked, the object will be saved regardless of whether it has changed. /// @@ -199,141 +622,162 @@ public static IDisposable AutoPersistCollection(this ReadOnlyO /// The interval to save the object on. Note that if an object is constantly changing, /// it is possible that it will never be saved. /// - /// A Disposable to disable automatic persistence. - public static IDisposable AutoPersistCollection(this TCollection @this, Func> doPersist, IObservable manualSaveSignal, TimeSpan? interval = null) // TODO: Create Test - where TItem : IReactiveObject - where TCollection : INotifyCollectionChanged, IEnumerable + /// A disposable to disable automatic persistence. + [RequiresUnreferencedCode( + "AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. " + + "In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. " + + "Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [RequiresDynamicCode( + "AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. " + + "In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. " + + "Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + public static IDisposable AutoPersistCollection( + this TCollection @this, + Func> doPersist, + IObservable manualSaveSignal, + TimeSpan? interval = null) + where TItem : IReactiveObject + where TCollection : INotifyCollectionChanged, IEnumerable { + ArgumentExceptionHelper.ThrowIfNull(@this); + ArgumentExceptionHelper.ThrowIfNull(doPersist); + ArgumentExceptionHelper.ThrowIfNull(manualSaveSignal); + + // Dictionary is used to preserve prior semantics: per-item disposable tracked by item instance. var disposerList = new Dictionary(); - var disposable = @this.ActOnEveryObject( - x => - { - if (disposerList.ContainsKey(x)) - { - return; - } - - disposerList[x] = x.AutoPersist(doPersist, manualSaveSignal, interval); - }, - x => - { - disposerList[x].Dispose(); - disposerList.Remove(x); - }); + var subscription = @this.ActOnEveryObject( + onAdd: x => + { + if (disposerList.TryGetValue(x, out _)) + { + return; + } + + disposerList[x] = x.AutoPersist(doPersist, manualSaveSignal, interval); + }, + onRemove: x => + { + if (!disposerList.TryGetValue(x, out var d)) + { + return; + } + + d.Dispose(); + disposerList.Remove(x); + }); return Disposable.Create(() => { - disposable.Dispose(); - disposerList.Values.ForEach(x => x.Dispose()); + subscription.Dispose(); + + foreach (var kvp in disposerList) + { + kvp.Value.Dispose(); + } + + disposerList.Clear(); }); } /// - /// Call methods 'onAdd' and 'onRemove' whenever an object is added or - /// removed from a collection. This class correctly handles both when + /// Call methods and whenever an object is added or + /// removed from a collection. This method correctly handles both when /// a collection is initialized, as well as when the collection is Reset. /// /// The item type. - /// - /// The reactive collection to watch for changes. - /// - /// - /// A method to be called when an object is added to the collection. - /// - /// - /// A method to be called when an object is removed from the collection. - /// - /// A Disposable that deactivates this behavior. - public static IDisposable ActOnEveryObject(this ObservableCollection @this, Action onAdd, Action onRemove) // TODO: Create Test - where TItem : IReactiveObject => - ActOnEveryObject>(@this, onAdd, onRemove); + /// The reactive collection to watch for changes. + /// A method to be called when an object is added to the collection. + /// A method to be called when an object is removed from the collection. + /// A disposable that deactivates this behavior. + public static IDisposable ActOnEveryObject( + this ObservableCollection @this, + Action onAdd, + Action onRemove) + where TItem : IReactiveObject + => ActOnEveryObject>(@this, onAdd, onRemove); /// - /// Call methods 'onAdd' and 'onRemove' whenever an object is added or - /// removed from a collection. This class correctly handles both when + /// Call methods and whenever an object is added or + /// removed from a collection. This method correctly handles both when /// a collection is initialized, as well as when the collection is Reset. /// /// The item type. - /// - /// The reactive collection to watch for changes. - /// - /// - /// A method to be called when an object is added to the collection. - /// - /// - /// A method to be called when an object is removed from the collection. - /// - /// A Disposable that deactivates this behavior. - public static IDisposable ActOnEveryObject(this ReadOnlyObservableCollection @this, Action onAdd, Action onRemove) // TODO: Create Test - where TItem : IReactiveObject => - ActOnEveryObject>(@this, onAdd, onRemove); + /// The reactive collection to watch for changes. + /// A method to be called when an object is added to the collection. + /// A method to be called when an object is removed from the collection. + /// A disposable that deactivates this behavior. + public static IDisposable ActOnEveryObject( + this ReadOnlyObservableCollection @this, + Action onAdd, + Action onRemove) + where TItem : IReactiveObject + => ActOnEveryObject>(@this, onAdd, onRemove); /// - /// Call methods 'onAdd' and 'onRemove' whenever an object is added or - /// removed from a collection. This class correctly handles both when + /// Call methods and whenever an object is added or + /// removed from a collection. This method correctly handles both when /// a collection is initialized, as well as when the collection is Reset. /// /// The item type. /// The collection type. - /// - /// The reactive collection to watch for changes. - /// - /// - /// A method to be called when an object is added to the collection. - /// - /// - /// A method to be called when an object is removed from the collection. - /// - /// A Disposable that deactivates this behavior. - public static IDisposable ActOnEveryObject(this TCollection collection, Action onAdd, Action onRemove) - where TItem : IReactiveObject - where TCollection : INotifyCollectionChanged, IEnumerable + /// The reactive collection to watch for changes. + /// A method to be called when an object is added to the collection. + /// A method to be called when an object is removed from the collection. + /// A disposable that deactivates this behavior. + public static IDisposable ActOnEveryObject( + this TCollection collection, + Action onAdd, + Action onRemove) + where TItem : IReactiveObject + where TCollection : INotifyCollectionChanged, IEnumerable { ArgumentExceptionHelper.ThrowIfNull(onAdd); ArgumentExceptionHelper.ThrowIfNull(onRemove); ArgumentExceptionHelper.ThrowIfNull(collection); - foreach (var v in collection) - { - onAdd(v); - } - + // ToObservableChangeSet will emit existing items when first subscribed, so we don't need to manually iterate them var changedDisposable = ActOnEveryObject(collection.ToObservableChangeSet(), onAdd, onRemove); return Disposable.Create(() => { changedDisposable.Dispose(); - collection.ForEach(onRemove); + foreach (var v in collection) + { + onRemove(v); + } }); } /// - /// Call methods 'onAdd' and 'onRemove' whenever an object is added or - /// removed from a collection. This class correctly handles both when + /// Call methods and whenever an object is added or + /// removed from a collection. This method correctly handles both when /// a collection is initialized, as well as when the collection is Reset. /// /// The item type. - /// - /// The reactive collection to watch for changes. - /// - /// - /// A method to be called when an object is added to the collection. - /// - /// - /// A method to be called when an object is removed from the collection. - /// - /// A Disposable that deactivates this behavior. - public static IDisposable ActOnEveryObject(this IObservable> @this, Action onAdd, Action onRemove) - where TItem : IReactiveObject => - @this.Subscribe(changeSet => + /// The observable change set to watch for changes. + /// A method to be called when an object is added to the collection. + /// A method to be called when an object is removed from the collection. + /// A disposable that deactivates this behavior. + public static IDisposable ActOnEveryObject( + this IObservable> @this, + Action onAdd, + Action onRemove) + where TItem : IReactiveObject + { + ArgumentExceptionHelper.ThrowIfNull(@this); + ArgumentExceptionHelper.ThrowIfNull(onAdd); + ArgumentExceptionHelper.ThrowIfNull(onRemove); + + return @this.Subscribe(changeSet => { foreach (var change in changeSet) { switch (change.Reason) { case ListChangeReason.Refresh: + // Preserve original ordering: remove all, then add all. foreach (var item in change.Range) { onRemove(item); @@ -385,4 +829,155 @@ public static IDisposable ActOnEveryObject(this IObservable + /// Gets metadata for a runtime type that is not statically known to the trimmer. + /// + /// The runtime type. + /// The computed persistence metadata. + /// + /// This path is trimming/AOT unsafe unless the application explicitly preserves the required members + /// (properties and related attribute metadata) for . + /// + [RequiresUnreferencedCode( + "AutoPersist reflects over the runtime type. In trimmed/AOT builds, required property/attribute metadata may be removed " + + "unless explicitly preserved. Prefer CreateMetadata() and the overloads that accept AutoPersistMetadata.")] + [RequiresDynamicCode( + "AutoPersist reflects over the runtime type. In trimmed/AOT builds, required property/attribute metadata may be removed " + + "unless explicitly preserved. Prefer CreateMetadata() and the overloads that accept AutoPersistMetadata.")] + private static PersistMetadata GetMetadataForUnknownRuntimeType(Type runtimeType) + => PersistMetadataByType.GetValue(runtimeType, static t => PersistMetadata.Create(t)); + + /// + /// Public-facing persistence metadata for AutoPersist. + /// + /// + /// This type exists so callers can provide persistence metadata explicitly in trimming/AOT scenarios, + /// avoiding runtime reflection over unknown types. + /// + public sealed class AutoPersistMetadata + { + /// + /// Initializes a new instance of the class. + /// + /// Whether the type is annotated with [DataContract]. + /// The set of property names annotated with [DataMember]. + /// + /// Thrown when is . + /// + public AutoPersistMetadata(bool hasDataContract, ISet persistablePropertyNames) + { + ArgumentExceptionHelper.ThrowIfNull(persistablePropertyNames); + + HasDataContract = hasDataContract; + PersistablePropertyNames = persistablePropertyNames; + } + + /// + /// Gets a value indicating whether the target type is annotated with [DataContract]. + /// + public bool HasDataContract { get; } + + /// + /// Gets the set of property names annotated with [DataMember]. + /// + public ISet PersistablePropertyNames { get; } + } + + /// + /// Holds precomputed metadata for a closed generic . + /// + /// + /// The type for which persistence metadata is computed. + /// + private static class PersistMetadataHolder< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T> + where T : IReactiveObject + { + /// + /// Gets the computed persistence metadata for . + /// + internal static readonly PersistMetadata Metadata = PersistMetadata.Create(typeof(T)); + } + + /// + /// Immutable persistence metadata for a given type. + /// + private sealed record PersistMetadata + { + /// + /// Initializes a new instance of the class. + /// + /// Whether the type is annotated with [DataContract]. + /// The set of property names annotated with [DataMember]. + private PersistMetadata(bool hasDataContract, HashSet persistablePropertyNames) + { + HasDataContract = hasDataContract; + PersistablePropertyNames = persistablePropertyNames; + Public = new AutoPersistMetadata(hasDataContract, persistablePropertyNames); + } + + /// + /// Gets a value indicating whether the target type is annotated with [DataContract]. + /// + internal bool HasDataContract { get; } + + /// + /// Gets the set of property names annotated with [DataMember]. + /// + internal HashSet PersistablePropertyNames { get; } + + /// + /// Gets a public metadata wrapper for callers. + /// + internal AutoPersistMetadata Public { get; } + + /// + /// Creates persistence metadata for a statically-known or explicitly-preserved type. + /// + /// The type to analyze. + /// The computed persistence metadata. + internal static PersistMetadata Create( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] + Type type) + { + // Preserve original semantics: [DataContract] is checked via GetCustomAttributes(..., inherit: true). + var hasDataContract = type.GetCustomAttributes(typeof(DataContractAttribute), inherit: true).Length > 0; + + // Preserve original semantics: consider DeclaredProperties only (not inherited properties). + // Use reflection flags directly to avoid GetTypeInfo() overhead. + var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); + + HashSet? set = null; + + for (var i = 0; i < properties.Length; i++) + { + var p = properties[i]; + if (!HasDataMemberAttribute(p)) + { + continue; + } + + set ??= new HashSet(StringComparer.Ordinal); + set.Add(p.Name); + } + + set ??= new HashSet(StringComparer.Ordinal); + return new PersistMetadata(hasDataContract, set); + } + + /// + /// Determines whether a property is annotated with [DataMember]. + /// + /// The property to inspect. + /// if the property is annotated; otherwise . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool HasDataMemberAttribute(PropertyInfo property) + { + // Avoid LINQ allocations; use IsDefined which is efficient for the common case. + // DataMemberAttribute is not inherited by default, but preserve inherit=true for parity. + return property.IsDefined(typeof(DataMemberAttribute), inherit: true); + } + } } diff --git a/src/ReactiveUI/Mixins/BuilderMixins.cs b/src/ReactiveUI/Mixins/BuilderMixins.cs index 06607f0156..b8b2fe5ddc 100644 --- a/src/ReactiveUI/Mixins/BuilderMixins.cs +++ b/src/ReactiveUI/Mixins/BuilderMixins.cs @@ -14,6 +14,88 @@ namespace ReactiveUI.Builder; /// public static class BuilderMixins { + /// + /// Registers view-to-viewmodel mappings inline using a fluent builder. + /// This method is fully AOT-compatible when all view types are known at compile time. + /// + /// The ReactiveUI builder instance. + /// Configuration action for registering views. + /// The builder for chaining. + /// Thrown when builder or configure is null. + /// Thrown when DefaultViewLocator is not registered in the service locator. + /// + /// + /// () + /// .RegisterViews(views => views + /// .Map() + /// .Map() + /// .Map()) + /// .Build(); + /// ]]> + /// + /// + public static IReactiveUIBuilder RegisterViews( + this IReactiveUIBuilder builder, + Action configure) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + ArgumentExceptionHelper.ThrowIfNull(configure); + + var viewLocator = AppLocator.Current.GetService() as DefaultViewLocator + ?? throw new InvalidOperationException( + "DefaultViewLocator must be registered before calling RegisterViews. " + + "Ensure you've called WithPlatformModule() or manually registered DefaultViewLocator."); + + var mappingBuilder = new ViewMappingBuilder(viewLocator); + configure(mappingBuilder); + return builder; + } + + /// + /// Registers views using a reusable view module. + /// This method is fully AOT-compatible when all view types are known at compile time. + /// + /// The view module type to register. + /// The ReactiveUI builder instance. + /// The builder for chaining. + /// Thrown when builder is null. + /// Thrown when DefaultViewLocator is not registered in the service locator. + /// + /// + /// (() => new LoginView()) + /// .Map(() => new RegisterView()); + /// } + /// } + /// + /// new ReactiveUIBuilder() + /// .WithPlatformModule() + /// .WithViewModule() + /// .Build(); + /// ]]> + /// + /// + public static IReactiveUIBuilder WithViewModule(this IReactiveUIBuilder builder) + where TModule : IViewModule, new() + { + ArgumentExceptionHelper.ThrowIfNull(builder); + + var viewLocator = AppLocator.Current.GetService() as DefaultViewLocator + ?? throw new InvalidOperationException( + "DefaultViewLocator must be registered before calling WithViewModule. " + + "Ensure you've called WithPlatformModule() or manually registered DefaultViewLocator."); + + var module = new TModule(); + module.RegisterViews(viewLocator); + return builder; + } + /// /// Configures the task pool scheduler. /// @@ -32,6 +114,27 @@ public static IReactiveUIBuilder WithTaskPoolScheduler(this IReactiveUIBuilder b return builder; } + /// + /// Builds and configures the application using the ReactiveUI builder pattern. + /// + /// Use this extension method to finalize application setup when working with ReactiveUI. This + /// method should be called after all necessary configuration has been applied to the builder. + /// The application builder to configure. Must implement . + /// An instance representing the configured application. + /// Thrown if does not implement . + public static IReactiveUIBuilder BuildApp(this IAppBuilder appBuilder) + { + ArgumentExceptionHelper.ThrowIfNull(appBuilder); + if (appBuilder is not IReactiveUIBuilder reactiveUiBuilder) + { + throw new InvalidOperationException( + "The provided IAppBuilder is not an IReactiveUIBuilder. Ensure you are using the ReactiveUI builder pattern."); + } + + reactiveUiBuilder.BuildApp(); + return reactiveUiBuilder; + } + /// /// Configures the main thread scheduler. /// @@ -93,10 +196,7 @@ public static IReactiveUIBuilder WithRegistration(this IReactiveUIBuilder builde /// The builder instance for chaining. /// /// builder. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif + [RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibility, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] public static IReactiveUIBuilder WithViewsFromAssembly(this IReactiveUIBuilder builder, Assembly assembly) { ArgumentExceptionHelper.ThrowIfNull(builder); @@ -114,10 +214,6 @@ public static IReactiveUIBuilder WithViewsFromAssembly(this IReactiveUIBuilder b /// The builder instance for method chaining. /// /// builder. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static IReactiveUIBuilder WithPlatformModule(this IReactiveUIBuilder builder) where T : IWantsToRegisterStuff, new() { @@ -219,6 +315,23 @@ public static IReactiveUIBuilder ConfigureMessageBus(this IReactiveUIBuilder rea return reactiveUIBuilder; } + /// + /// Registers a custom message bus instance. + /// + /// The reactive UI builder. + /// The message bus instance to use. + /// + /// The builder instance for chaining. + /// + /// reactiveUIBuilder. + public static IReactiveUIBuilder WithMessageBus(this IReactiveUIBuilder reactiveUIBuilder, IMessageBus messageBus) + { + ArgumentExceptionHelper.ThrowIfNull(reactiveUIBuilder); + + reactiveUIBuilder.WithMessageBus(messageBus); + return reactiveUIBuilder; + } + /// /// Configures the ReactiveUI view locator. /// @@ -1016,4 +1129,196 @@ public static IReactiveUIInstance WithInstance + /// Registers a typed binding converter using the concrete type. + /// + /// The source type for the conversion. + /// The target type for the conversion. + /// The ReactiveUI builder. + /// The converter instance to register. + /// The builder instance for chaining. + /// Thrown if builder or converter is null. + public static IReactiveUIBuilder WithConverter( + this IReactiveUIBuilder builder, + BindingTypeConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + builder.WithConverter(converter); + return builder; + } + + /// + /// Registers a typed binding converter using the interface. + /// + /// The ReactiveUI builder. + /// The converter instance to register. + /// The builder instance for chaining. + /// Thrown if builder or converter is null. + public static IReactiveUIBuilder WithConverter( + this IReactiveUIBuilder builder, + IBindingTypeConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + builder.WithConverter(converter); + return builder; + } + + /// + /// Registers a typed binding converter via factory (lazy instantiation). + /// + /// The source type for the conversion. + /// The target type for the conversion. + /// The ReactiveUI builder. + /// The factory function that creates the converter. + /// The builder instance for chaining. + /// Thrown if builder or factory is null. + public static IReactiveUIBuilder WithConverter( + this IReactiveUIBuilder builder, + Func> factory) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + builder.WithConverter(factory); + return builder; + } + + /// + /// Registers a typed binding converter via factory (interface, lazy instantiation). + /// + /// The ReactiveUI builder. + /// The factory function that creates the converter. + /// The builder instance for chaining. + /// Thrown if builder or factory is null. + public static IReactiveUIBuilder WithConverter( + this IReactiveUIBuilder builder, + Func factory) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + builder.WithConverter(factory); + return builder; + } + + /// + /// Registers multiple typed converters at once. + /// + /// The ReactiveUI builder. + /// The converters to register. + /// The builder instance for chaining. + /// Thrown if builder or converters is null. + public static IReactiveUIBuilder WithConverters( + this IReactiveUIBuilder builder, + params IBindingTypeConverter[] converters) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + ArgumentExceptionHelper.ThrowIfNull(converters); + + foreach (var converter in converters) + { + builder.WithConverter(converter); + } + + return builder; + } + + /// + /// Registers a fallback binding converter. + /// + /// The ReactiveUI builder. + /// The fallback converter instance to register. + /// The builder instance for chaining. + /// Thrown if builder or converter is null. + /// + /// Fallback converters are used when no exact type-pair converter is found. + /// They perform runtime type checking via . + /// + public static IReactiveUIBuilder WithFallbackConverter( + this IReactiveUIBuilder builder, + IBindingFallbackConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + builder.WithFallbackConverter(converter); + return builder; + } + + /// + /// Registers a fallback binding converter via factory (lazy instantiation). + /// + /// The ReactiveUI builder. + /// The factory function that creates the fallback converter. + /// The builder instance for chaining. + /// Thrown if builder or factory is null. + public static IReactiveUIBuilder WithFallbackConverter( + this IReactiveUIBuilder builder, + Func factory) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + builder.WithFallbackConverter(factory); + return builder; + } + + /// + /// Registers a set-method binding converter. + /// + /// The ReactiveUI builder. + /// The set-method converter instance to register. + /// The builder instance for chaining. + /// Thrown if builder or converter is null. + /// + /// Set-method converters are used for special binding scenarios where the target + /// uses a method (e.g., TableLayoutPanel.SetColumn) instead of a property setter. + /// + public static IReactiveUIBuilder WithSetMethodConverter( + this IReactiveUIBuilder builder, + ISetMethodBindingConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + builder.WithSetMethodConverter(converter); + return builder; + } + + /// + /// Registers a set-method binding converter via factory (lazy instantiation). + /// + /// The ReactiveUI builder. + /// The factory function that creates the set-method converter. + /// The builder instance for chaining. + /// Thrown if builder or factory is null. + public static IReactiveUIBuilder WithSetMethodConverter( + this IReactiveUIBuilder builder, + Func factory) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + builder.WithSetMethodConverter(factory); + return builder; + } + + /// + /// Imports all converters from a Splat dependency resolver into the builder. + /// + /// The ReactiveUI builder. + /// The Splat resolver to import converters from. + /// The builder instance for chaining. + /// Thrown if builder or resolver is null. + /// + /// + /// This is a migration helper to ease transition from Splat-based registration + /// to the new ConverterService-based registration. + /// + /// + /// This method imports all three converter types: + /// + /// Typed converters () + /// Fallback converters () + /// Set-method converters () + /// + /// + /// + public static IReactiveUIBuilder WithConvertersFrom( + this IReactiveUIBuilder builder, + IReadonlyDependencyResolver resolver) + { + ArgumentExceptionHelper.ThrowIfNull(builder); + builder.WithConvertersFrom(resolver); + return builder; + } } diff --git a/src/ReactiveUI/Mixins/DependencyResolverMixins.cs b/src/ReactiveUI/Mixins/DependencyResolverMixins.cs index 63a1dd620c..ead5eb47a5 100644 --- a/src/ReactiveUI/Mixins/DependencyResolverMixins.cs +++ b/src/ReactiveUI/Mixins/DependencyResolverMixins.cs @@ -5,8 +5,6 @@ using System.Reflection; -using Splat.Builder; - namespace ReactiveUI; /// @@ -15,73 +13,6 @@ namespace ReactiveUI; [Preserve(AllMembers = true)] public static class DependencyResolverMixins { - /// - /// This method allows you to initialize resolvers with the default - /// ReactiveUI types. All resolvers used as the default - /// AppLocator.Current. - /// If no namespaces are passed in, all registrations will be checked. - /// - /// The resolver to initialize. - /// Which platforms to use. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("InitializeReactiveUI uses reflection to locate types which may be trimmed.")] - [RequiresUnreferencedCode("InitializeReactiveUI uses reflection to locate types which may be trimmed.")] -#endif - public static void InitializeReactiveUI(this IMutableDependencyResolver resolver, params RegistrationNamespace[] registrationNamespaces) - { - if (AppBuilder.UsingBuilder && !ModeDetector.InUnitTestRunner() && ReferenceEquals(resolver, AppLocator.CurrentMutable)) - { - // If the builder has been used for the default resolver in a non-test environment, - // do not re-register defaults via reflection for AppLocator.CurrentMutable. - return; - } - - ArgumentExceptionHelper.ThrowIfNull(resolver); - ArgumentExceptionHelper.ThrowIfNull(registrationNamespaces); - - var possibleNamespaces = new Dictionary - { - { RegistrationNamespace.Winforms, "ReactiveUI.Winforms" }, - { RegistrationNamespace.Wpf, "ReactiveUI.Wpf" }, - { RegistrationNamespace.Uno, "ReactiveUI.Uno" }, - { RegistrationNamespace.UnoWinUI, "ReactiveUI.Uno.WinUI" }, - { RegistrationNamespace.Blazor, "ReactiveUI.Blazor" }, - { RegistrationNamespace.Drawing, "ReactiveUI.Drawing" }, - { RegistrationNamespace.Avalonia, "ReactiveUI.Avalonia" }, - { RegistrationNamespace.Maui, "ReactiveUI.Maui" }, - { RegistrationNamespace.Uwp, "ReactiveUI.Uwp" }, - { RegistrationNamespace.WinUI, "ReactiveUI.WinUI" }, - }; - - if (registrationNamespaces.Length == 0) - { - registrationNamespaces = PlatformRegistrationManager.DefaultRegistrationNamespaces; - } - - var extraNs = - registrationNamespaces - .Where(ns => possibleNamespaces.ContainsKey(ns)) - .Select(ns => possibleNamespaces[ns]) - .ToArray(); - - // Set up the built-in registration - new Registrations().Register((f, t) => resolver.RegisterConstant(f(), t)); -#if NET6_0_OR_GREATER - new PlatformRegistrations().Register((f, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] t) => resolver.RegisterConstant(f(), t)); -#else - new PlatformRegistrations().Register((f, t) => resolver.RegisterConstant(f(), t)); -#endif - - var fdr = typeof(DependencyResolverMixins); - - var assemblyName = new AssemblyName(fdr.AssemblyQualifiedName!.Replace(fdr.FullName + ", ", string.Empty)); - - foreach (var ns in extraNs) - { - ProcessRegistrationForNamespace(ns, assemblyName, resolver); - } - } - /// /// Registers inside the Splat dependency container all the classes that derive off /// IViewFor using Reflection. This is a easy way to register all the Views @@ -89,10 +20,7 @@ public static void InitializeReactiveUI(this IMutableDependencyResolver resolver /// /// The dependency injection resolver to register the Views with. /// The assembly to search using reflection for IViewFor classes. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RegisterViewsForViewModels scans the provided assembly and creates instances via reflection; this is not compatible with AOT.")] - [RequiresUnreferencedCode("RegisterViewsForViewModels uses reflection over types and members which may be trimmed.")] -#endif + [RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibility, use the ReactiveUIBuilder pattern to register views explicitly.")] public static void RegisterViewsForViewModels(this IMutableDependencyResolver resolver, Assembly assembly) { ArgumentExceptionHelper.ThrowIfNull(resolver); @@ -123,11 +51,13 @@ public static void RegisterViewsForViewModels(this IMutableDependencyResolver re } } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RegisterType creates instances via Activator.CreateInstance which requires dynamic code generation in AOT.")] - [RequiresUnreferencedCode("RegisterType uses reflection to locate parameterless constructors which may be trimmed.")] -#endif - private static void RegisterType(IMutableDependencyResolver resolver, TypeInfo ti, Type serviceType, string contract) + private static void RegisterType( + IMutableDependencyResolver resolver, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + TypeInfo ti, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type serviceType, + string contract) { var factory = TypeFactory(ti); if (ti.GetCustomAttribute() is not null) @@ -140,13 +70,8 @@ private static void RegisterType(IMutableDependencyResolver resolver, TypeInfo t } } -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("TypeFactory uses reflection to invoke parameterless constructors which may be trimmed.")] -#endif private static Func TypeFactory( -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] -#endif + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] TypeInfo typeInfo) { var parameterlessConstructor = typeInfo.DeclaredConstructors.FirstOrDefault(ci => ci.IsPublic && ci.GetParameters().Length == 0); @@ -155,50 +80,4 @@ private static Func TypeFactory( : () => Activator.CreateInstance(typeInfo.AsType()) ?? throw new Exception($"Failed to instantiate type {typeInfo.FullName} - ensure it has a public parameterless constructor."); } - -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("ProcessRegistrationForNamespace uses reflection to locate types which may be trimmed.")] - [RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] -#endif - private static void ProcessRegistrationForNamespace(string namespaceName, AssemblyName assemblyName, IMutableDependencyResolver resolver) - { - var targetTypeName = namespaceName + ".Registrations"; - - // Preferred path: find the target assembly by simple name among loaded assemblies - var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == namespaceName); - Type? registerTypeClass = null; - if (asm is null) - { - try - { - asm = Assembly.Load(new AssemblyName(namespaceName)); - } - catch - { - asm = null; - } - } - - if (asm is not null) - { - registerTypeClass = asm.GetType(targetTypeName, throwOnError: false, ignoreCase: false); - } - - // Fallback to legacy lookup using full name synthesis - if (registerTypeClass is null && assemblyName.Name is not null) - { - var fullName = targetTypeName + ", " + assemblyName.FullName.Replace(assemblyName.Name, namespaceName); - registerTypeClass = Reflection.ReallyFindType(fullName, false); - } - - if (registerTypeClass is not null) - { - var registerer = (IWantsToRegisterStuff)Activator.CreateInstance(registerTypeClass)!; -#if NET6_0_OR_GREATER - registerer.Register((f, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] t) => resolver.RegisterConstant(f(), t)); -#else - registerer.Register((f, t) => resolver.RegisterConstant(f(), t)); -#endif - } - } } diff --git a/src/ReactiveUI/Mixins/ExpressionMixins.cs b/src/ReactiveUI/Mixins/ExpressionMixins.cs index b45cc0459c..74ca438943 100644 --- a/src/ReactiveUI/Mixins/ExpressionMixins.cs +++ b/src/ReactiveUI/Mixins/ExpressionMixins.cs @@ -19,10 +19,6 @@ public static class ExpressionMixins /// /// The expression. /// An enumerable of expressions. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Expression chain analysis requires dynamic code generation")] - [RequiresUnreferencedCode("Expression chain analysis may reference members that could be trimmed")] -#endif public static IEnumerable GetExpressionChain(this Expression expression) { var expressions = new List(); @@ -90,10 +86,6 @@ public static IEnumerable GetExpressionChain(this Expression express /// /// The expression. /// The member info from the expression. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Member info access requires dynamic code generation")] - [RequiresUnreferencedCode("Member info access may reference members that could be trimmed")] -#endif public static MemberInfo? GetMemberInfo(this Expression expression) { ArgumentExceptionHelper.ThrowIfNull(expression); @@ -121,10 +113,6 @@ public static IEnumerable GetExpressionChain(this Expression express /// /// The expression. /// The parent expression. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Expression analysis requires dynamic code generation")] - [RequiresUnreferencedCode("Expression analysis may reference members that could be trimmed")] -#endif public static Expression? GetParent(this Expression expression) // TODO: Create Test { ArgumentExceptionHelper.ThrowIfNull(expression); diff --git a/src/ReactiveUI/Mixins/MutableDependencyResolverAOTExtensions.cs b/src/ReactiveUI/Mixins/MutableDependencyResolverAOTExtensions.cs index 57da048797..73287b9137 100644 --- a/src/ReactiveUI/Mixins/MutableDependencyResolverAOTExtensions.cs +++ b/src/ReactiveUI/Mixins/MutableDependencyResolverAOTExtensions.cs @@ -3,6 +3,8 @@ // 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; + namespace ReactiveUI; /// @@ -11,29 +13,42 @@ namespace ReactiveUI; /// internal static class MutableDependencyResolverAOTExtensions { -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Generic registration does not use reflection")] - [UnconditionalSuppressMessage("AOT", "IL3050:Members annotated with 'RequiresDynamicCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Generic registration does not use dynamic code")] -#endif + /// + /// Initializes static members of the class. + /// + static MutableDependencyResolverAOTExtensions() => RxAppBuilder.EnsureInitialized(); + internal static IMutableDependencyResolver RegisterViewForViewModelAOT(this IMutableDependencyResolver resolver, string? contract = null) where TView : class, IViewFor, new() where TViewModel : class { ArgumentExceptionHelper.ThrowIfNull(resolver); - resolver.Register(static () => new TView(), typeof(IViewFor), contract ?? string.Empty); + if (contract is null) + { + resolver.Register>(static () => new TView()); + } + else + { + resolver.Register>(static () => new TView(), contract); + } + return resolver; } -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Generic registration does not use reflection")] - [UnconditionalSuppressMessage("AOT", "IL3050:Members annotated with 'RequiresDynamicCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Generic registration does not use dynamic code")] -#endif internal static IMutableDependencyResolver RegisterSingletonViewForViewModelAOT(this IMutableDependencyResolver resolver, string? contract = null) where TView : class, IViewFor, new() where TViewModel : class { ArgumentExceptionHelper.ThrowIfNull(resolver); - resolver.RegisterLazySingleton(static () => new TView(), typeof(IViewFor), contract ?? string.Empty); + if (contract is null) + { + resolver.RegisterLazySingleton>(static () => new TView()); + } + else + { + resolver.RegisterLazySingleton>(static () => new TView(), contract); + } + return resolver; } } diff --git a/src/ReactiveUI/Mixins/MutableDependencyResolverExtensions.cs b/src/ReactiveUI/Mixins/MutableDependencyResolverExtensions.cs index d037e485cc..041a33402b 100644 --- a/src/ReactiveUI/Mixins/MutableDependencyResolverExtensions.cs +++ b/src/ReactiveUI/Mixins/MutableDependencyResolverExtensions.cs @@ -3,6 +3,8 @@ // 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; + namespace ReactiveUI; /// @@ -11,6 +13,11 @@ namespace ReactiveUI; /// public static class MutableDependencyResolverExtensions { + /// + /// Initializes static members of the class. + /// + static MutableDependencyResolverExtensions() => RxAppBuilder.EnsureInitialized(); + /// /// Registers a view for a view model via generics without reflection. /// @@ -19,16 +26,20 @@ public static class MutableDependencyResolverExtensions /// Resolver to register into. /// Optional contract. /// The resolver, for chaining. -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Generic registration does not use reflection")] - [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Generic registration does not use dynamic code")] -#endif public static IMutableDependencyResolver RegisterViewForViewModel(this IMutableDependencyResolver resolver, string? contract = null) where TView : class, IViewFor, new() where TViewModel : class { ArgumentExceptionHelper.ThrowIfNull(resolver); - resolver.Register(static () => new TView(), typeof(IViewFor), contract ?? string.Empty); + if (contract is null) + { + resolver.Register>(static () => new TView()); + } + else + { + resolver.Register>(static () => new TView(), contract); + } + return resolver; } @@ -40,16 +51,20 @@ public static IMutableDependencyResolver RegisterViewForViewModelResolver to register into. /// Optional contract. /// The resolver, for chaining. -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Generic registration does not use reflection")] - [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Generic registration does not use dynamic code")] -#endif public static IMutableDependencyResolver RegisterSingletonViewForViewModel(this IMutableDependencyResolver resolver, string? contract = null) where TView : class, IViewFor, new() where TViewModel : class { ArgumentExceptionHelper.ThrowIfNull(resolver); - resolver.RegisterLazySingleton(static () => new TView(), typeof(IViewFor), contract ?? string.Empty); + if (contract is null) + { + resolver.RegisterLazySingleton>(static () => new TView()); + } + else + { + resolver.RegisterLazySingleton>(static () => new TView(), contract); + } + return resolver; } } diff --git a/src/ReactiveUI/Mixins/ObservableLoggingMixin.cs b/src/ReactiveUI/Mixins/ObservableLoggingMixin.cs index 83e7457689..d37c014fb4 100644 --- a/src/ReactiveUI/Mixins/ObservableLoggingMixin.cs +++ b/src/ReactiveUI/Mixins/ObservableLoggingMixin.cs @@ -5,6 +5,8 @@ using System.Globalization; +using ReactiveUI.Builder; + namespace ReactiveUI; /// @@ -12,6 +14,11 @@ namespace ReactiveUI; /// public static class ObservableLoggingMixin { + /// + /// Initializes static members of the class. + /// + static ObservableLoggingMixin() => RxAppBuilder.EnsureInitialized(); + /// /// Logs an Observable to Splat's Logger. /// diff --git a/src/ReactiveUI/Mixins/ObservableMixins.cs b/src/ReactiveUI/Mixins/ObservableMixins.cs index 6896b8a4d7..ba413f982a 100644 --- a/src/ReactiveUI/Mixins/ObservableMixins.cs +++ b/src/ReactiveUI/Mixins/ObservableMixins.cs @@ -3,6 +3,8 @@ // 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; + namespace ReactiveUI; /// @@ -10,6 +12,11 @@ namespace ReactiveUI; /// public static class ObservableMixins { + /// + /// Initializes static members of the class. + /// + static ObservableMixins() => RxAppBuilder.EnsureInitialized(); + /// /// Returns only values that are not null. /// Converts the nullability. diff --git a/src/ReactiveUI/Mixins/ObservedChangedMixin.cs b/src/ReactiveUI/Mixins/ObservedChangedMixin.cs index 77d83852be..b9e84696c4 100644 --- a/src/ReactiveUI/Mixins/ObservedChangedMixin.cs +++ b/src/ReactiveUI/Mixins/ObservedChangedMixin.cs @@ -3,6 +3,8 @@ // 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; + namespace ReactiveUI; /// @@ -10,6 +12,11 @@ namespace ReactiveUI; /// public static class ObservedChangedMixin { + /// + /// Initializes static members of the class. + /// + static ObservedChangedMixin() => RxAppBuilder.EnsureInitialized(); + /// /// Returns the name of a property which has been changed. /// @@ -19,10 +26,6 @@ public static class ObservedChangedMixin /// /// The name of the property which has changed. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("TryGetValue uses expression chain analysis and reflection which require dynamic code generation.")] - [RequiresUnreferencedCode("TryGetValue uses expression chain analysis and reflection which may reference members that could be trimmed.")] -#endif public static string GetPropertyName(this IObservedChange item) => item is null ? throw new ArgumentNullException(nameof(item)) @@ -40,10 +43,7 @@ item is null /// /// The current value of the property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetValue uses expression chain analysis and reflection which require dynamic code generation.")] - [RequiresUnreferencedCode("GetValue uses expression chain analysis and reflection which may reference members that could be trimmed.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static TValue GetValue(this IObservedChange item) => item is null ? throw new ArgumentNullException(nameof(item)) @@ -63,10 +63,7 @@ item is null /// /// The current value of the property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetValueOrDefault uses expression chain analysis and reflection which require dynamic code generation.")] - [RequiresUnreferencedCode("GetValueOrDefault uses expression chain analysis and reflection which may reference members that could be trimmed.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static TValue? GetValueOrDefault(this IObservedChange item) => // TODO: Create Test item is null ? throw new ArgumentNullException(nameof(item)) : !item.TryGetValue(out var returnValue) ? default : returnValue; @@ -83,10 +80,7 @@ item is null /// An Observable representing the stream of current values of /// the given change notification stream. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Value method uses GetValue which requires expression chain analysis and reflection.")] - [RequiresUnreferencedCode("Value method uses GetValue which may reference members that could be trimmed.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable Value(this IObservable> item) => // TODO: Create Test item.Select(GetValue); @@ -106,10 +100,7 @@ public static IObservable Value(this IObservable /// True if the entire expression was able to be followed, false otherwise. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("TryGetValue uses expression chain analysis and reflection which require dynamic code generation.")] - [RequiresUnreferencedCode("TryGetValue uses expression chain analysis and reflection which may reference members that could be trimmed.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] internal static bool TryGetValue(this IObservedChange item, out TValue changeValue) { if (!Equals(item.Value, default(TValue))) @@ -140,10 +131,7 @@ internal static bool TryGetValue(this IObservedChange /// The target property to apply the change to. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("TryGetValue uses expression chain analysis and reflection which require dynamic code generation.")] - [RequiresUnreferencedCode("TryGetValue uses expression chain analysis and reflection which may reference members that could be trimmed.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] internal static void SetValueToProperty( this IObservedChange item, TTarget target, diff --git a/src/ReactiveUI/Mixins/ReactiveNotifyPropertyChangedMixin.cs b/src/ReactiveUI/Mixins/ReactiveNotifyPropertyChangedMixin.cs index f8e5393c32..6c1bfd0575 100644 --- a/src/ReactiveUI/Mixins/ReactiveNotifyPropertyChangedMixin.cs +++ b/src/ReactiveUI/Mixins/ReactiveNotifyPropertyChangedMixin.cs @@ -3,6 +3,8 @@ // 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; + namespace ReactiveUI; /// @@ -10,21 +12,28 @@ namespace ReactiveUI; /// Reactive Notify Property Changed based events. /// [Preserve(AllMembers = true)] +[RequiresUnreferencedCode( + "Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")] public static class ReactiveNotifyPropertyChangedMixin { - [SuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Marked as Preserve")] - [SuppressMessage("Trimming", "IL2026:Calling members annotated with 'RequiresUnreferencedCodeAttribute' may break functionality when trimming application code.", Justification = "Marked as Preserve")] - 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, - RxApp.BigCacheLimit); - - static ReactiveNotifyPropertyChangedMixin() => RxApp.EnsureInitialized(); + 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); + + /// + /// Initializes static members of the class. + /// + static ReactiveNotifyPropertyChangedMixin() => RxAppBuilder.EnsureInitialized(); /// /// ObservableForProperty returns an Observable representing the @@ -41,10 +50,8 @@ public static class ReactiveNotifyPropertyChangedMixin /// If true, the Observable will not notify with the initial value. /// If set to true, values are filtered with DistinctUntilChanged. /// An Observable representing the property change notifications for the given property name. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode( + "Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")] public static IObservable> ObservableForProperty( this TSender? item, string propertyName, @@ -69,16 +76,20 @@ public static IObservable> ObservableForPropert } var factory = _notifyFactoryCache.Get((item!.GetType(), propertyName, beforeChange)) - ?? throw new Exception($"Could not find a ICreatesObservableForProperty for {item!.GetType()} property {propertyName}. This should never happen, your service locator is probably broken. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."); + ?? throw new Exception( + $"Could not find a ICreatesObservableForProperty for {item!.GetType()} property {propertyName}. This should never happen, your service locator is probably broken. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."); // Helper to get current property value without expression analysis. static TValue GetCurrentValue(object sender, string name) { var t = sender.GetType(); #if NETSTANDARD || NETFRAMEWORK - var prop = t.GetProperty(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.FlattenHierarchy); + var prop = + t.GetProperty(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.FlattenHierarchy); #else - var prop = t.GetProperty(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.FlattenHierarchy); + var prop = t.GetProperty( + name, + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.FlattenHierarchy); #endif if (prop is null) { @@ -147,14 +158,17 @@ static TValue GetCurrentValue(object sender, string name) /// The source object to observe properties of. /// The property name to observe. /// An observable sequence of observed changes for the given property name. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode( + "Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")] public static IObservable> ObservableForProperty( this TSender? item, string propertyName) - => ObservableForProperty(item, propertyName, beforeChange: false, skipInitial: true, isDistinct: true); + => ObservableForProperty( + item, + propertyName, + beforeChange: false, + skipInitial: true, + isDistinct: true); /// /// ObservableForProperty overload that avoids expression trees by using a property name and beforeChange option. @@ -165,15 +179,18 @@ public static IObservable> ObservableForPropert /// The property name to observe. /// If true, the observable will notify immediately before a property is going to change. /// An observable sequence of observed changes for the given property name. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode( + "Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")] public static IObservable> ObservableForProperty( this TSender? item, string propertyName, bool beforeChange) - => ObservableForProperty(item, propertyName, beforeChange: beforeChange, skipInitial: true, isDistinct: true); + => ObservableForProperty( + item, + propertyName, + beforeChange: beforeChange, + skipInitial: true, + isDistinct: true); /// /// ObservableForProperty overload that avoids expression trees by using a property name with options to control initial emission and beforeChange. @@ -185,16 +202,19 @@ public static IObservable> ObservableForPropert /// If true, the observable will notify immediately before a property is going to change. /// If true, the observable will not notify with the initial value. /// An observable sequence of observed changes for the given property name. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode( + "Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")] public static IObservable> ObservableForProperty( this TSender? item, string propertyName, bool beforeChange, bool skipInitial) - => ObservableForProperty(item, propertyName, beforeChange: beforeChange, skipInitial: skipInitial, isDistinct: true); + => ObservableForProperty( + item, + propertyName, + beforeChange: beforeChange, + skipInitial: skipInitial, + isDistinct: true); /// /// ObservableForProperty returns an Observable representing the @@ -212,10 +232,7 @@ public static IObservable> ObservableForPropert /// An Observable representing the property change /// notifications for the given property. /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable> ObservableForProperty( this TSender? item, Expression> property) => ObservableForProperty(item, property, false, true, true); @@ -238,10 +255,7 @@ public static IObservable> ObservableForPropert /// An Observable representing the property change /// notifications for the given property. /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable> ObservableForProperty( this TSender? item, Expression> property, @@ -267,10 +281,7 @@ public static IObservable> ObservableForPropert /// An Observable representing the property change /// notifications for the given property. /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable> ObservableForProperty( this TSender? item, Expression> property, @@ -298,10 +309,7 @@ public static IObservable> ObservableForPropert /// An Observable representing the property change /// notifications for the given property. /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable> ObservableForProperty( this TSender? item, Expression> property, @@ -327,11 +335,11 @@ public static IObservable> ObservableForPropert */ return SubscribeToExpressionChain( - item, - property.Body, - beforeChange, - skipInitial, - isDistinct); + item, + property.Body, + beforeChange, + skipInitial, + isDistinct); } /// @@ -350,10 +358,7 @@ public static IObservable> ObservableForPropert /// item. /// An Observable representing the property change /// notifications for the given property. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable ObservableForProperty( this TSender? item, Expression> property, @@ -384,10 +389,7 @@ public static IObservable ObservableForProperty( /// immediately before a property is going to change. /// An Observable representing the property change /// notifications for the given property. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable ObservableForProperty( this TSender? item, Expression> property, @@ -414,14 +416,11 @@ public static IObservable ObservableForProperty( /// A observable which notifies about observed changes. /// /// If we cannot cast from the target value from the specified last property. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable> SubscribeToExpressionChain( this TSender? source, Expression? expression) // TODO: Create Test - => SubscribeToExpressionChain(source, expression, false, true, false, true); + => SubscribeToExpressionChain(source, expression, false, true, false, true); /// /// Creates a observable which will subscribe to the each property and sub property @@ -437,15 +436,12 @@ public static IObservable> SubscribeToExpressio /// A observable which notifies about observed changes. /// /// If we cannot cast from the target value from the specified last property. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable> SubscribeToExpressionChain( this TSender? source, Expression? expression, bool beforeChange) // TODO: Create Test - => SubscribeToExpressionChain(source, expression, beforeChange, true, false, true); + => SubscribeToExpressionChain(source, expression, beforeChange, true, false, true); /// /// Creates a observable which will subscribe to the each property and sub property @@ -462,16 +458,13 @@ public static IObservable> SubscribeToExpressio /// A observable which notifies about observed changes. /// /// If we cannot cast from the target value from the specified last property. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable> SubscribeToExpressionChain( this TSender? source, Expression? expression, bool beforeChange, bool skipInitial) // TODO: Create Test - => SubscribeToExpressionChain(source, expression, beforeChange, skipInitial, false, true); + => SubscribeToExpressionChain(source, expression, beforeChange, skipInitial, false, true); /// /// Creates a observable which will subscribe to the each property and sub property @@ -489,17 +482,20 @@ public static IObservable> SubscribeToExpressio /// A observable which notifies about observed changes. /// /// If we cannot cast from the target value from the specified last property. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable> SubscribeToExpressionChain( this TSender? source, Expression? expression, bool beforeChange, bool skipInitial, bool suppressWarnings) // TODO: Create Test - => SubscribeToExpressionChain(source, expression, beforeChange, skipInitial, suppressWarnings, true); + => SubscribeToExpressionChain( + source, + expression, + beforeChange, + skipInitial, + suppressWarnings, + true); /// /// Creates a observable which will subscribe to the each property and sub property @@ -518,10 +514,7 @@ public static IObservable> SubscribeToExpressio /// A observable which notifies about observed changes. /// /// If we cannot cast from the target value from the specified last property. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IObservable> SubscribeToExpressionChain( this TSender? source, Expression? expression, @@ -534,9 +527,11 @@ public static IObservable> SubscribeToExpressio Observable.Return(new ObservedChange(null, null, source)); var chain = Reflection.Rewrite(expression).GetExpressionChain(); - notifier = chain.Aggregate(notifier, (n, expr) => n - .Select(y => NestedObservedChanges(expr, y, beforeChange, suppressWarnings)) - .Switch()); + notifier = chain.Aggregate( + notifier, + (n, expr) => n + .Select(y => NestedObservedChanges(expr, y, beforeChange, suppressWarnings)) + .Switch()); if (skipInitial) { @@ -557,19 +552,15 @@ public static IObservable> SubscribeToExpressio return new ObservedChange(source!, expression, (TValue)val!); }); - if (isDistinct) - { - return r.DistinctUntilChanged(x => x.Value); - } - - return r; + return isDistinct ? r.DistinctUntilChanged(x => x.Value) : r; } -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - private static IObservable> NestedObservedChanges(Expression expression, IObservedChange sourceChange, bool beforeChange, bool suppressWarnings) + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + private static IObservable> NestedObservedChanges( + Expression expression, + IObservedChange sourceChange, + bool beforeChange, + bool suppressWarnings) { // Make sure a change at a root node propagates events down var kicker = new ObservedChange(sourceChange.Value, expression, default); @@ -582,25 +573,29 @@ public static IObservable> SubscribeToExpressio // Handle non null values in the chain return NotifyForProperty(sourceChange.Value, expression, beforeChange, suppressWarnings) - .StartWith(kicker) - .Select(static x => new ObservedChange(x.Sender, x.Expression, x.GetValueOrDefault())); + .StartWith(kicker) + .Select(static x => new ObservedChange(x.Sender, x.Expression, x.GetValueOrDefault())); } -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif - private static IObservable> NotifyForProperty(object sender, Expression expression, bool beforeChange, bool suppressWarnings) + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + private static IObservable> NotifyForProperty( + object sender, + Expression expression, + bool beforeChange, + bool suppressWarnings) { ArgumentExceptionHelper.ThrowIfNull(expression); - var memberInfo = expression.GetMemberInfo() ?? throw new ArgumentException("The expression does not have valid member info", nameof(expression)); + var memberInfo = expression.GetMemberInfo() ?? throw new ArgumentException( + "The expression does not have valid member info", + nameof(expression)); var propertyName = memberInfo.Name; var result = _notifyFactoryCache.Get((sender.GetType(), propertyName, beforeChange)); return result switch { - null => throw new Exception($"Could not find a ICreatesObservableForProperty for {sender.GetType()} property {propertyName}. This should never happen, your service locator is probably broken. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."), + null => throw new InvalidOperationException( + $"Could not find a ICreatesObservableForProperty for {sender.GetType()} property {propertyName}. This should never happen, your service locator is probably broken. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."), _ => result.GetNotificationForProperty(sender, expression, propertyName, beforeChange, suppressWarnings) }; } diff --git a/src/ReactiveUI/ObservableForProperty/INPCObservableForProperty.cs b/src/ReactiveUI/ObservableForProperty/INPCObservableForProperty.cs index 25a432d123..4906b7a7ef 100644 --- a/src/ReactiveUI/ObservableForProperty/INPCObservableForProperty.cs +++ b/src/ReactiveUI/ObservableForProperty/INPCObservableForProperty.cs @@ -13,10 +13,7 @@ namespace ReactiveUI; public class INPCObservableForProperty : ICreatesObservableForProperty { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged) { var target = beforeChanged ? typeof(INotifyPropertyChanging) : typeof(INotifyPropertyChanged); @@ -24,10 +21,7 @@ public int GetAffinityForObject(Type type, string propertyName, bool beforeChang } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { ArgumentExceptionHelper.ThrowIfNull(expression); diff --git a/src/ReactiveUI/ObservableForProperty/IROObservableForProperty.cs b/src/ReactiveUI/ObservableForProperty/IROObservableForProperty.cs index 46bae6a297..1253fca043 100644 --- a/src/ReactiveUI/ObservableForProperty/IROObservableForProperty.cs +++ b/src/ReactiveUI/ObservableForProperty/IROObservableForProperty.cs @@ -8,60 +8,79 @@ namespace ReactiveUI; /// -/// Generates Observables based on observing Reactive objects. +/// Generates observables for instances by subscribing to their change notifications. /// -public class IROObservableForProperty : ICreatesObservableForProperty +/// +/// +/// This implementation filters the change stream for a specific property name and projects each matching notification to +/// an . +/// +/// +/// Trimming/AOT: is annotated for trimming/AOT in this codebase. This type +/// repeats the required annotations on its public members to satisfy the interface contract. +/// +/// +public sealed class IROObservableForProperty : ICreatesObservableForProperty { - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif + /// + /// + /// This implementation returns a higher affinity than the INPC-based implementation because every + /// also implements property change notification and should be preferred when available. + /// + /// The runtime type to query. + /// The property name to query. + /// + /// If , indicates the caller requests notifications before the property value changes. + /// If , indicates after-change notifications. + /// + /// + /// A positive integer if supported; zero otherwise. + /// + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) { - // NB: Since every IReactiveObject is also an INPC, we need to bind more - // tightly than INPCObservableForProperty, so we return 10 here - // instead of one -#pragma warning disable IDE0022 // Use expression body for methods + ArgumentExceptionHelper.ThrowIfNull(type); + ArgumentExceptionHelper.ThrowIfNull(propertyName); + + // NB: Since every IReactiveObject is also an INPC, we need to bind more tightly than INPCObservableForProperty. return typeof(IReactiveObject).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()) ? 10 : 0; -#pragma warning restore IDE0022 // Use expression body for methods } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] -#endif - public IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) + /// + /// Thrown when is . + /// Thrown when does not implement . + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + public IObservable> GetNotificationForProperty( + object sender, + Expression expression, + string propertyName, + bool beforeChanged = false, + bool suppressWarnings = false) { + ArgumentExceptionHelper.ThrowIfNull(sender); ArgumentExceptionHelper.ThrowIfNull(expression); + ArgumentExceptionHelper.ThrowIfNull(propertyName); if (sender is not IReactiveObject iro) { - throw new ArgumentException("Sender doesn't implement IReactiveObject"); + throw new ArgumentException("Sender doesn't implement IReactiveObject", nameof(sender)); } - var obs = beforeChanged ? iro.GetChangingObservable() : iro.GetChangedObservable(); - - if (beforeChanged) - { - if (expression.NodeType == ExpressionType.Index) - { - return obs.Where(x => x.PropertyName?.Equals(propertyName + "[]", StringComparison.InvariantCulture) == true) - .Select(_ => new ObservedChange(sender, expression, default)); - } + // For indexers, ReactiveObject reports "PropertyName[]". + var observedName = + expression.NodeType == ExpressionType.Index + ? string.Concat(propertyName, "[]") + : propertyName; - return obs.Where(x => x.PropertyName?.Equals(propertyName, StringComparison.InvariantCulture) == true) - .Select(_ => new ObservedChange(sender, expression, default)); - } + // Preserve the original comparison semantics. + const StringComparison comparison = StringComparison.InvariantCulture; - if (expression.NodeType == ExpressionType.Index) - { - return obs.Where(x => x.PropertyName?.Equals(propertyName + "[]", StringComparison.InvariantCulture) == true) - .Select(_ => new ObservedChange(sender, expression, default)); - } + var source = beforeChanged ? iro.GetChangingObservable() : iro.GetChangedObservable(); - return obs.Where(x => x.PropertyName?.Equals(propertyName, StringComparison.InvariantCulture) == true) - .Select(_ => new ObservedChange(sender, expression, default)); + // Keep the projection allocation-free; avoid repeating the same query shape. + return source + .Where(x => x.PropertyName is not null && x.PropertyName.Equals(observedName, comparison)) + .Select(static _ => default(object)) + .Select(_ => new ObservedChange(sender, expression, default)); } } diff --git a/src/ReactiveUI/ObservableForProperty/OAPHCreationHelperMixin.cs b/src/ReactiveUI/ObservableForProperty/OAPHCreationHelperMixin.cs index b8e25b1885..dfdfd30bfb 100644 --- a/src/ReactiveUI/ObservableForProperty/OAPHCreationHelperMixin.cs +++ b/src/ReactiveUI/ObservableForProperty/OAPHCreationHelperMixin.cs @@ -40,10 +40,6 @@ public static class OAPHCreationHelperMixin /// An initialized ObservableAsPropertyHelper; use this as the backing field /// for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -92,10 +88,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing field /// for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -139,10 +131,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing field /// for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -189,10 +177,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing /// field for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -249,10 +233,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing /// field for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -300,10 +280,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing /// field for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -355,10 +331,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing field /// for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -400,10 +372,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing field /// for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -454,10 +422,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing field /// for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -508,10 +472,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing /// field for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -567,10 +527,6 @@ public static ObservableAsPropertyHelper ToProperty( /// An initialized ObservableAsPropertyHelper; use this as the backing /// field for your property. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif public static ObservableAsPropertyHelper ToProperty( this IObservable target, TObj source, @@ -591,10 +547,6 @@ public static ObservableAsPropertyHelper ToProperty( return result; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced code")] -#endif internal static ObservableAsPropertyHelper ObservableToProperty( this TObj target, IObservable observable, @@ -632,10 +584,6 @@ internal static ObservableAsPropertyHelper ObservableToProperty ObservableToProperty( this TObj target, IObservable observable, @@ -672,10 +620,6 @@ internal static ObservableAsPropertyHelper ObservableToProperty ObservableToProperty( this TObj target, IObservable observable, @@ -698,10 +642,6 @@ internal static ObservableAsPropertyHelper ObservableToProperty ObservableToProperty( this TObj target, IObservable observable, diff --git a/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs b/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs index 6653c45ecf..6a5262f4e9 100644 --- a/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs +++ b/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs @@ -63,10 +63,6 @@ public sealed class ObservableAsPropertyHelper : IHandleObservableErrors, IDi /// The scheduler that the notifications will be provided on - /// this should normally be a Dispatcher-based scheduler. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] -#endif public ObservableAsPropertyHelper( IObservable observable, Action onChanged, @@ -104,10 +100,6 @@ public ObservableAsPropertyHelper( /// The scheduler that the notifications will provided on - this /// should normally be a Dispatcher-based scheduler. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] -#endif public ObservableAsPropertyHelper( IObservable observable, Action onChanged, @@ -146,10 +138,6 @@ public ObservableAsPropertyHelper( /// The scheduler that the notifications will provided on - this /// should normally be a Dispatcher-based scheduler. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] -#endif public ObservableAsPropertyHelper( IObservable observable, Action onChanged, @@ -164,7 +152,7 @@ public ObservableAsPropertyHelper( scheduler ??= CurrentThreadScheduler.Instance; onChanging ??= _ => { }; - _thrownExceptions = new Lazy>(() => new ScheduledSubject(CurrentThreadScheduler.Instance, RxApp.DefaultExceptionHandler)); + _thrownExceptions = new Lazy>(() => new ScheduledSubject(CurrentThreadScheduler.Instance, RxState.DefaultExceptionHandler)); _subject = new ScheduledSubject(scheduler); _subject.Subscribe( @@ -249,10 +237,6 @@ public T Value /// normally be a Dispatcher-based scheduler. /// /// A default property helper. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] -#endif public static ObservableAsPropertyHelper Default(T? initialValue = default, IScheduler? scheduler = null) => // TODO: Create Test new(Observable.Never, static _ => { }, initialValue!, false, scheduler); diff --git a/src/ReactiveUI/ObservableForProperty/POCOObservableForProperty.cs b/src/ReactiveUI/ObservableForProperty/POCOObservableForProperty.cs index 8fe0a2ef39..c9bd43b44b 100644 --- a/src/ReactiveUI/ObservableForProperty/POCOObservableForProperty.cs +++ b/src/ReactiveUI/ObservableForProperty/POCOObservableForProperty.cs @@ -4,42 +4,96 @@ // See the LICENSE file in the project root for full license information. using System.Collections.Concurrent; +using System.Runtime.CompilerServices; namespace ReactiveUI; /// -/// This class is the final fallback for WhenAny, and will simply immediately -/// return the value of the type at the time it was created. It will also -/// warn the user that this is probably not what they want to do. +/// Final fallback implementation for WhenAny-style observation when no observable mechanism is available. /// -public class POCOObservableForProperty : ICreatesObservableForProperty +/// +/// +/// This implementation emits exactly one value (the current value at subscription time) and then never emits again. +/// +/// +/// If warnings are enabled, it logs a warning once per (runtime type, property name) pair to help callers detect +/// accidental POCO usage in observation chains. +/// +/// +/// Trimming/AOT: is annotated for trimming/AOT in this codebase; this type +/// repeats the required annotations on its public members to satisfy the interface contract. +/// +/// +public sealed class POCOObservableForProperty : ICreatesObservableForProperty { - private static readonly ConcurrentDictionary<(Type, string), bool> _hasWarned = new(); + /// + /// Tracks whether a warning has been logged for a given (runtime type, property name) pair. + /// + /// + /// This is a process-wide cache intended to avoid repeated warnings. It can grow with unique observed pairs. + /// + private static readonly ConcurrentDictionary<(Type Type, string PropertyName), byte> HasWarned = new(); - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses reflection and type analysis")] - [RequiresUnreferencedCode("GetAffinityForObject may reference members that could be trimmed")] -#endif - public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) => 1; + /// + /// + /// This fallback returns a very low affinity to ensure it is only used when no more specific implementation applies. + /// + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) + { + ArgumentExceptionHelper.ThrowIfNull(type); + ArgumentExceptionHelper.ThrowIfNull(propertyName); - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses reflection and type analysis")] - [RequiresUnreferencedCode("GetNotificationForProperty may reference members that could be trimmed")] -#endif - public IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) + return 1; + } + + /// + /// Thrown when is . + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + public IObservable> GetNotificationForProperty( + object sender, + Expression expression, + string propertyName, + bool beforeChanged = false, + bool suppressWarnings = false) { ArgumentExceptionHelper.ThrowIfNull(sender); + ArgumentExceptionHelper.ThrowIfNull(expression); + ArgumentExceptionHelper.ThrowIfNull(propertyName); + + if (!suppressWarnings) + { + WarnOnce(sender, propertyName); + } + + // Emit one value, then never complete to preserve legacy WhenAny semantics. + return Observable + .Return(new ObservedChange(sender, expression, default), RxSchedulers.MainThreadScheduler) + .Concat(Observable>.Never); + } + /// + /// Logs a POCO observation warning at most once per (runtime type, property name) pair. + /// + /// The observed object. + /// The observed property name. +#if NET8_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void WarnOnce(object sender, string propertyName) + { + // Hot path considerations: + // - Avoid ContainsKey + indexer (two lookups). + // - Use TryAdd as the single atomic gate. var type = sender.GetType(); - if (!_hasWarned.ContainsKey((type, propertyName)) && !suppressWarnings) + if (!HasWarned.TryAdd((type, propertyName), 0)) { - this.Log().Warn($"The class {type.FullName} property {propertyName} is a POCO type and won't send change notifications, WhenAny will only return a single value!"); - _hasWarned[(type, propertyName)] = true; + return; } - return Observable.Return(new ObservedChange(sender, expression, default), RxApp.MainThreadScheduler) - .Concat(Observable>.Never); + this.Log().Warn( + $"The class {type.FullName} property {propertyName} is a POCO type and won't send change notifications, WhenAny will only return a single value!"); } } diff --git a/src/ReactiveUI/ObservableFuncMixins.cs b/src/ReactiveUI/ObservableFuncMixins.cs index a6a1822408..f14b66ffa9 100644 --- a/src/ReactiveUI/ObservableFuncMixins.cs +++ b/src/ReactiveUI/ObservableFuncMixins.cs @@ -22,10 +22,7 @@ public static class ObservableFuncMixins /// /// An observable Result. /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif + [RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static IObservable ToObservable( this Expression> expression, TSource? source, @@ -35,7 +32,7 @@ public static class ObservableFuncMixins ArgumentExceptionHelper.ThrowIfNull(expression); var sParam = Reflection.Rewrite(expression.Body); - return source.SubscribeToExpressionChain(sParam, beforeChange, skipInitial, RxApp.SuppressViewCommandBindingMessage) + return source.SubscribeToExpressionChain(sParam, beforeChange, skipInitial, RxSchedulers.SuppressViewCommandBindingMessage) .Select(static x => x.GetValue()) .Retry(); } diff --git a/src/ReactiveUI/PlatformRegistrationManager.cs b/src/ReactiveUI/PlatformRegistrationManager.cs deleted file mode 100644 index 04c9a7b5d6..0000000000 --- a/src/ReactiveUI/PlatformRegistrationManager.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2025 .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; - -/// -/// Class that represents the platform registration for ReactiveUI. -/// -public static class PlatformRegistrationManager -{ - internal static RegistrationNamespace[] DefaultRegistrationNamespaces { get; } = -#if NET6_0_OR_GREATER - Enum.GetValues(); -#else - (RegistrationNamespace[])Enum.GetValues(typeof(RegistrationNamespace)); -#endif - - internal static RegistrationNamespace[] NamespacesToRegister { get; set; } = DefaultRegistrationNamespaces; - - /// - /// Set the platform namespaces to register. - /// This needs to be set before the first call to . - /// - /// The namespaces to register. - public static void SetRegistrationNamespaces(params RegistrationNamespace[] namespaces) => NamespacesToRegister = namespaces; -} diff --git a/src/ReactiveUI/Platforms/android/AndroidCommandBinders.cs b/src/ReactiveUI/Platforms/android/AndroidCommandBinders.cs index 147d524632..7f423afd9b 100644 --- a/src/ReactiveUI/Platforms/android/AndroidCommandBinders.cs +++ b/src/ReactiveUI/Platforms/android/AndroidCommandBinders.cs @@ -10,19 +10,43 @@ namespace ReactiveUI; /// -/// Android implementation that provides binding to an ICommand in the ViewModel to a Control -/// in the View. +/// Android implementation that provides binding to an ICommand in the ViewModel to a control in the View. /// [Preserve(AllMembers = true)] -public class AndroidCommandBinders : FlexibleCommandBinder +public sealed class AndroidCommandBinders : FlexibleCommandBinder { /// /// Initializes a new instance of the class. /// - [SuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Marked as Preserve")] + /// + /// Thrown when the Enabled property cannot be found on , which is required for binding. + /// public AndroidCommandBinders() { - var view = typeof(View); - Register(view, 9, (cmd, t, cp) => ForEvent(cmd, t, cp, "Click", view.GetRuntimeProperty("Enabled") ?? throw new InvalidOperationException("Could not find property 'Enabled' on type View, which is needed for binding"))); + var viewType = typeof(View); + + // Cache reflection metadata once at registration time. + var enabledProperty = + viewType.GetRuntimeProperty("Enabled") + ?? throw new InvalidOperationException( + "Could not find property 'Enabled' on type View, which is needed for binding"); + + // Precompute the setter once; ForEvent will no-op enabled sync if null (but for View it should exist). + var enabledSetter = Reflection.GetValueSetterForProperty(enabledProperty); + + Register(viewType, 9, (cmd, target, commandParameter) => + { + // Keep existing behavior: ForEvent throws if cmd is null. + // Also keep the "null commandParameter means use target" idiom (handled inside ForEvent overload). + var view = (View)target!; + + return ForEvent( + cmd, + view, + commandParameter, + addHandler: h => view.Click += h, + removeHandler: h => view.Click -= h, + enabledSetter: enabledSetter); + }); } } diff --git a/src/ReactiveUI/Platforms/android/AndroidObservableForWidgets.cs b/src/ReactiveUI/Platforms/android/AndroidObservableForWidgets.cs index 19a09d59ff..033ea67fe0 100644 --- a/src/ReactiveUI/Platforms/android/AndroidObservableForWidgets.cs +++ b/src/ReactiveUI/Platforms/android/AndroidObservableForWidgets.cs @@ -3,6 +3,8 @@ // 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.Frozen; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Versioning; using Android.OS; @@ -15,173 +17,390 @@ namespace ReactiveUI; /// -/// Android view objects are not Generally Observable™, so hard-code some -/// particularly useful types. +/// Provides property change notifications for a curated set of Android widget types which are not generally observable +/// through standard property change mechanisms. /// +/// +/// +/// This implementation only supports after-change notifications; beforeChange is not supported. +/// +/// +/// A static dispatch table maps widget types and property names to observable factory functions. +/// +/// +/// Trimming/AOT: this type repeats the trimming/AOT annotations required by the +/// interface on its implementing members to satisfy the interface contract. +/// +/// [Preserve(AllMembers = true)] -public class AndroidObservableForWidgets : ICreatesObservableForProperty +public sealed class AndroidObservableForWidgets : ICreatesObservableForProperty { - private static readonly Dictionary<(Type viewType, string? propertyName), Func>>> _dispatchTable; + /// + /// Stores observable factory functions keyed by (widget type, property name). + /// + /// + /// This table is immutable after type initialization and is safe for concurrent reads. + /// + private static readonly FrozenDictionary<(Type ViewType, string PropertyName), Func>>> DispatchTable; + /// + /// Stores, per property name, the set of widget types that can produce notifications for that property. + /// + /// + /// This index supports efficient affinity checks and dispatch selection without scanning the entire dispatch table. + /// + private static readonly FrozenDictionary TypesByPropertyName; + + /// + /// Initializes static members of the class. + /// Initializes the static dispatch tables for the supported Android widgets. + /// + /// + /// This constructor runs once and constructs immutable lookup tables for fast concurrent reads. + /// [ObsoletedOSPlatform("android23.0")] [SupportedOSPlatform("android23.0")] - static AndroidObservableForWidgets() => - _dispatchTable = new[] + static AndroidObservableForWidgets() + { + var items = new[] { - CreateFromWidget(static v => v.Text, static (v, h) => v.TextChanged += h, static (v, h) => v.TextChanged -= h), - CreateFromWidget(static v => v.Value, static (v, h) => v.ValueChanged += h, static (v, h) => v.ValueChanged -= h), - CreateFromWidget(static v => v.Rating, static (v, h) => v.RatingBarChange += h, static (v, h) => v.RatingBarChange -= h), - CreateFromWidget(static v => v.Checked, static (v, h) => v.CheckedChange += h, static (v, h) => v.CheckedChange -= h), - CreateFromWidget(static v => v.Date, static (v, h) => v.DateChange += h, static (v, h) => v.DateChange -= h), - CreateFromWidget(static v => v.CurrentTab, static (v, h) => v.TabChanged += h, static (v, h) => v.TabChanged -= h), + CreateFromWidget( + static v => v.Text, + static (v, h) => v.TextChanged += h, + static (v, h) => v.TextChanged -= h), + + CreateFromWidget( + static v => v.Value, + static (v, h) => v.ValueChanged += h, + static (v, h) => v.ValueChanged -= h), + + CreateFromWidget( + static v => v.Rating, + static (v, h) => v.RatingBarChange += h, + static (v, h) => v.RatingBarChange -= h), + + CreateFromWidget( + static v => v.Checked, + static (v, h) => v.CheckedChange += h, + static (v, h) => v.CheckedChange -= h), + + CreateFromWidget( + static v => v.Date, + static (v, h) => v.DateChange += h, + static (v, h) => v.DateChange -= h), + + CreateFromWidget( + static v => v.CurrentTab, + static (v, h) => v.TabChanged += h, + static (v, h) => v.TabChanged -= h), + CreateTimePickerHourFromWidget(), CreateTimePickerMinuteFromWidget(), CreateFromAdapterView(), - }.ToDictionary(static k => (viewType: k.Type, propertyName: k.Property), static v => v.Func); + }; + + var dispatch = + new Dictionary<(Type ViewType, string PropertyName), Func>>>( + capacity: items.Length); + + var byProperty = + new Dictionary>(capacity: items.Length, comparer: StringComparer.Ordinal); + + for (var i = 0; i < items.Length; i++) + { + var item = items[i]; + + if (item.Property is null) + { + continue; + } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses reflection for property access and type checking which require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses reflection for property access and type checking which may require unreferenced code")] -#endif + dispatch[(item.Type, item.Property)] = item.Func; + + if (!byProperty.TryGetValue(item.Property, out var list)) + { + list = new List(capacity: 2); + byProperty.Add(item.Property, list); + } + + list.Add(item.Type); + } + + DispatchTable = dispatch.ToFrozenDictionary(); + + var index = new Dictionary(byProperty.Count, StringComparer.Ordinal); + foreach (var pair in byProperty) + { + index[pair.Key] = pair.Value.ToArray(); + } + + TypesByPropertyName = index.ToFrozenDictionary(StringComparer.Ordinal); + } + + /// + /// + /// This implementation does not support before-change notifications. + /// + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(propertyName); + if (beforeChanged) { return 0; } - return _dispatchTable.Keys.Any(x => x.viewType?.IsAssignableFrom(type) == true && x.propertyName?.Equals(propertyName) == true) ? 5 : 0; + if (!TypesByPropertyName.TryGetValue(propertyName, out var candidates)) + { + return 0; + } + + for (var i = 0; i < candidates.Length; i++) + { + if (candidates[i].IsAssignableFrom(type)) + { + return 5; + } + } + + return 0; } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses reflection for property access and type checking which require dynamic code generation")] - [RequiresUnreferencedCode("GetNotificationForProperty uses reflection for property access and type checking which may require unreferenced code")] -#endif - public IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) + /// + /// + /// This implementation does not support before-change notifications. + /// + /// + /// Thrown when , , or is + /// . + /// + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + public IObservable> GetNotificationForProperty( + object sender, + Expression expression, + string propertyName, + bool beforeChanged = false, + bool suppressWarnings = false) { - var type = sender?.GetType(); - var tableItem = _dispatchTable.Keys.First(x => x.viewType?.IsAssignableFrom(type) == true && x.propertyName?.Equals(propertyName) == true); + ArgumentNullException.ThrowIfNull(sender); + ArgumentNullException.ThrowIfNull(expression); + ArgumentNullException.ThrowIfNull(propertyName); + + if (beforeChanged) + { + return Observable.Never>(); + } + + var senderType = sender.GetType(); + + if (!TypesByPropertyName.TryGetValue(propertyName, out var candidates)) + { + return Observable.Never>(); + } - return !_dispatchTable.TryGetValue(tableItem, out var dispatchItem) ? - Observable.Never>() : - dispatchItem.Invoke(sender!, expression); + for (var i = 0; i < candidates.Length; i++) + { + var candidateType = candidates[i]; + + if (!candidateType.IsAssignableFrom(senderType)) + { + continue; + } + + return DispatchTable.TryGetValue((candidateType, propertyName), out var factory) + ? factory(sender, expression) + : Observable.Never>(); + } + + return Observable.Never>(); } + /// + /// Creates a dispatch item for selection changes on instances. + /// + /// + /// Adapter selection is represented by two distinct events: and + /// . This dispatch item merges both into a single observable sequence. + /// + /// + /// A dispatch item mapping and the SelectedItem property to an observable factory. + /// private static DispatchItem CreateFromAdapterView() { - // AdapterView is more complicated because there are two events - one for select and one for deselect - // respond to both - const string PropName = "SelectedItem"; + const string propName = "SelectedItem"; return new DispatchItem( - typeof(AdapterView), - PropName, - (x, ex) => - { - var adapterView = (AdapterView)x; - - var itemSelected = - Observable - .FromEvent, ObservedChange - >( - eventHandler => - { - void Handler(object? sender, AdapterView.ItemSelectedEventArgs e) => - eventHandler(new ObservedChange(adapterView, ex, default)); - - return Handler; - }, - h => adapterView.ItemSelected += h, - h => adapterView.ItemSelected -= h); - - var nothingSelected = - Observable - .FromEvent, - ObservedChange>( - eventHandler => - { - void Handler(object? sender, AdapterView.NothingSelectedEventArgs e) => - eventHandler(new ObservedChange(adapterView, ex, default)); - - return Handler; - }, - h => adapterView.NothingSelected += h, - h => adapterView.NothingSelected -= h); - - return itemSelected.Merge(nothingSelected); - }); + typeof(AdapterView), + propName, + (x, ex) => + { + var adapterView = (AdapterView)x; + + var itemSelected = + Observable.FromEvent, ObservedChange>( + eventHandler => + { + void Handler(object? unusedSender, AdapterView.ItemSelectedEventArgs unusedEventArgs) => + eventHandler(new ObservedChange(adapterView, ex, default)); + + return Handler; + }, + h => adapterView.ItemSelected += h, + h => adapterView.ItemSelected -= h); + + var nothingSelected = + Observable.FromEvent, ObservedChange>( + eventHandler => + { + void Handler(object? unusedSender, AdapterView.NothingSelectedEventArgs unusedEventArgs) => + eventHandler(new ObservedChange(adapterView, ex, default)); + + return Handler; + }, + h => adapterView.NothingSelected += h, + h => adapterView.NothingSelected -= h); + + return itemSelected.Merge(nothingSelected); + }); } + /// + /// Creates a dispatch item for the hour property that is compatible with the current OS level. + /// + /// + /// Android introduced at API level 23. Earlier OS versions use + /// . + /// + /// A dispatch item for observing the hour value on a . [ObsoletedOSPlatform("android23.0")] [SupportedOSPlatform("android23.0")] private static DispatchItem CreateTimePickerHourFromWidget() { if ((int)Build.VERSION.SdkInt >= 23) { - return CreateFromWidget(static v => v.Hour, static (v, h) => v.TimeChanged += h, static (v, h) => v.TimeChanged -= h); + return CreateFromWidget( + static v => v.Hour, + static (v, h) => v.TimeChanged += h, + static (v, h) => v.TimeChanged -= h); } - return CreateFromWidget(static v => v.CurrentHour, static (v, h) => v.TimeChanged += h, static (v, h) => v.TimeChanged -= h); + return CreateFromWidget( + static v => v.CurrentHour, + static (v, h) => v.TimeChanged += h, + static (v, h) => v.TimeChanged -= h); } + /// + /// Creates a dispatch item for the minute property that is compatible with the current OS level. + /// + /// + /// Android introduced at API level 23. Earlier OS versions use + /// . + /// + /// A dispatch item for observing the minute value on a . [ObsoletedOSPlatform("android23.0")] [SupportedOSPlatform("android23.0")] private static DispatchItem CreateTimePickerMinuteFromWidget() { if ((int)Build.VERSION.SdkInt >= 23) { - return CreateFromWidget(static v => v.Minute, static (v, h) => v.TimeChanged += h, static (v, h) => v.TimeChanged -= h); + return CreateFromWidget( + static v => v.Minute, + static (v, h) => v.TimeChanged += h, + static (v, h) => v.TimeChanged -= h); } - return CreateFromWidget(static v => v.CurrentMinute, static (v, h) => v.TimeChanged += h, static (v, h) => v.TimeChanged -= h); + return CreateFromWidget( + static v => v.CurrentMinute, + static (v, h) => v.TimeChanged += h, + static (v, h) => v.TimeChanged -= h); } - [SuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Marked as Preserve")] - [SuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Marked as Preserve")] - private static DispatchItem CreateFromWidget(Expression> property, Action> addHandler, Action> removeHandler) + /// + /// Creates a dispatch item for a widget type and property by subscribing to a widget event. + /// + /// + /// + /// The observable produced by the dispatch item emits a change record when the widget event fires. + /// + /// + /// Trimming/AOT: this helper uses expression inspection to derive the property name. It is preserved and suppressed + /// to avoid trimming/AOT analysis noise in supported platform builds. + /// + /// + /// The widget type. + /// The event args type for the widget event. + /// An expression selecting the widget property that is being observed. + /// Registers the event handler on the widget. + /// Unregisters the event handler from the widget. + /// A dispatch item for the widget and property. + /// Thrown when does not expose valid member info. + private static DispatchItem CreateFromWidget( + Expression> property, + Action> addHandler, + Action> removeHandler) where TView : View where TEventArgs : EventArgs { - var memberInfo = property.Body.GetMemberInfo() ?? throw new ArgumentException("Does not have a valid body member info.", nameof(property)); + var memberInfo = + property.Body.GetMemberInfo() ?? + throw new ArgumentException("Does not have a valid body member info.", nameof(property)); - // ExpressionToPropertyNames is used here as it handles boxing expressions that might - // occur due to our use of object + // ExpressionToPropertyNames is used here as it handles boxing expressions that might occur due to our use of object. var propName = memberInfo.Name; return new DispatchItem( - typeof(TView), - propName, - (x, ex) => - { - var view = (TView)x; - - return Observable.FromEvent, ObservedChange>( - eventHandler => - { - void Handler(object? sender, TEventArgs e) => - eventHandler(new ObservedChange(view, ex, default)); - - return Handler; - }, - h => addHandler(view, h), - h => removeHandler(view, h)); - }); + typeof(TView), + propName, + (x, ex) => + { + var view = (TView)x; + + return Observable.FromEvent, ObservedChange>( + eventHandler => + { + void Handler(object? unusedSender, TEventArgs unusedEventArgs) => + eventHandler(new ObservedChange(view, ex, default)); + + return Handler; + }, + h => addHandler(view, h), + h => removeHandler(view, h)); + }); } + /// + /// Represents a single dispatch table entry for a widget type and property. + /// private sealed record DispatchItem { + /// + /// Initializes a new instance of the class. + /// + /// The widget type for which observation is supported. + /// The property name that is observable for the widget type. + /// The observable factory function. public DispatchItem( Type type, string? property, Func>> func) => (Type, Property, Func) = (type, property, func); + /// + /// Gets the widget type for which observation is supported. + /// public Type Type { get; } + /// + /// Gets the property name that is observable for the widget type. + /// public string? Property { get; } + /// + /// Gets the observable factory function for the widget type and property. + /// public Func>> Func { get; } } } diff --git a/src/ReactiveUI/Platforms/android/AutoSuspendHelper.cs b/src/ReactiveUI/Platforms/android/AutoSuspendHelper.cs index 2b275d1dd4..1f4ebf41a2 100644 --- a/src/ReactiveUI/Platforms/android/AutoSuspendHelper.cs +++ b/src/ReactiveUI/Platforms/android/AutoSuspendHelper.cs @@ -14,7 +14,7 @@ namespace ReactiveUI; /// /// /// Register this helper inside your subclass to translate Activity lifecycle callbacks into -/// signals. The helper automatically distinguishes cold starts from restores by +/// signals. The helper automatically distinguishes cold starts from restores by /// inspecting and routes pause/save events to via /// . /// @@ -36,18 +36,14 @@ namespace ReactiveUI; /// { /// base.OnCreate(); /// _autoSuspendHelper = new AutoSuspendHelper(this); -/// RxApp.SuspensionHost.CreateNewAppState = () => new ShellState(); -/// RxApp.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(FilesDir!.AbsolutePath)); +/// RxSuspension.SuspensionHost.CreateNewAppState = () => new ShellState(); +/// RxSuspension.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(FilesDir!.AbsolutePath)); /// } /// } /// ]]> /// /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("AutoSuspendHelper uses RxApp.SuspensionHost which requires dynamic code generation")] -[RequiresUnreferencedCode("AutoSuspendHelper uses RxApp.SuspensionHost which may require unreferenced code")] -#endif public class AutoSuspendHelper : IEnableLogger, IDisposable { private readonly Subject _onCreate = new(); @@ -72,11 +68,11 @@ public AutoSuspendHelper(Application hostApplication) // TODO: Create Test _onCreate.Merge(_onSaveInstanceState).Subscribe(static x => LatestBundle = x); - RxApp.SuspensionHost.IsLaunchingNew = _onCreate.Where(static x => x is null).Select(static _ => Unit.Default); - RxApp.SuspensionHost.IsResuming = _onCreate.Where(static x => x is not null).Select(static _ => Unit.Default); - RxApp.SuspensionHost.IsUnpausing = _onRestart; - RxApp.SuspensionHost.ShouldPersistState = _onPause.Select(static _ => Disposable.Empty); - RxApp.SuspensionHost.ShouldInvalidateState = UntimelyDemise; + RxSuspension.SuspensionHost.IsLaunchingNew = _onCreate.Where(static x => x is null).Select(static _ => Unit.Default); + RxSuspension.SuspensionHost.IsResuming = _onCreate.Where(static x => x is not null).Select(static _ => Unit.Default); + RxSuspension.SuspensionHost.IsUnpausing = _onRestart; + RxSuspension.SuspensionHost.ShouldPersistState = _onPause.Select(static _ => Disposable.Empty); + RxSuspension.SuspensionHost.ShouldInvalidateState = UntimelyDemise; } /// diff --git a/src/ReactiveUI/Platforms/android/BundleSuspensionDriver.cs b/src/ReactiveUI/Platforms/android/BundleSuspensionDriver.cs index 7a9cb2db3d..62406bc5ce 100644 --- a/src/ReactiveUI/Platforms/android/BundleSuspensionDriver.cs +++ b/src/ReactiveUI/Platforms/android/BundleSuspensionDriver.cs @@ -5,58 +5,126 @@ using System.IO; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; namespace ReactiveUI; /// -/// Loads and saves state to persistent storage. +/// Loads and saves application state using the platform bundle. /// -public class BundleSuspensionDriver : ISuspensionDriver +/// +/// +/// This driver supports both legacy reflection-based System.Text.Json serialization +/// and trimming/AOT-safe serialization via source-generated . +/// +/// +public sealed class BundleSuspensionDriver : ISuspensionDriver { - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("LoadState uses JsonSerializer.Deserialize which requires dynamic code generation")] - [RequiresUnreferencedCode("LoadState uses JsonSerializer.Deserialize which may require unreferenced code")] -#endif - public IObservable LoadState() // TODO: Create Test + private const string StateKey = "__state"; + + /// + [RequiresUnreferencedCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer LoadState(JsonTypeInfo) for trimming or AOT scenarios.")] + [RequiresDynamicCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer LoadState(JsonTypeInfo) for trimming or AOT scenarios.")] + public IObservable LoadState() { try { - // NB: Sometimes OnCreate gives us a null bundle + // NB: Sometimes OnCreate gives us a null bundle. if (AutoSuspendHelper.LatestBundle is null) { - return Observable.Throw(new Exception("New bundle, start from scratch")); + return Observable.Throw( + new InvalidOperationException("New bundle detected; no persisted state is available.")); } - var buffer = AutoSuspendHelper.LatestBundle.GetByteArray("__state"); - + var buffer = AutoSuspendHelper.LatestBundle.GetByteArray(StateKey); if (buffer is null) { - return Observable.Throw(new InvalidOperationException("The buffer __state could not be found.")); + return Observable.Throw( + new InvalidOperationException("The persisted state buffer could not be found.")); } - var st = new MemoryStream(buffer); + return Observable.FromAsync(async () => + { + await using var stream = new MemoryStream(buffer, writable: false); + return await JsonSerializer.DeserializeAsync(stream).ConfigureAwait(false); + }); + } + catch (Exception ex) + { + return Observable.Throw(ex); + } + } + + /// + [RequiresUnreferencedCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer SaveState(T, JsonTypeInfo) for trimming or AOT scenarios.")] + [RequiresDynamicCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer SaveState(T, JsonTypeInfo) for trimming or AOT scenarios.")] + public IObservable SaveState(T state) + { + try + { + using var stream = new MemoryStream(); + JsonSerializer.Serialize(stream, state); - return Observable.FromAsync(async () => await JsonSerializer.DeserializeAsync(st)); + AutoSuspendHelper.LatestBundle?.PutByteArray(StateKey, stream.ToArray()); + return Observables.Unit; } catch (Exception ex) { - return Observable.Throw(ex); + return Observable.Throw(ex); } } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SaveState uses JsonSerializer.Serialize which requires dynamic code generation")] - [RequiresUnreferencedCode("SaveState uses JsonSerializer.Serialize which may require unreferenced code")] -#endif - public IObservable SaveState(object state) // TODO: Create Test + /// + public IObservable LoadState(JsonTypeInfo typeInfo) { + ArgumentNullException.ThrowIfNull(typeInfo); + try { - var st = new MemoryStream(); - JsonSerializer.Serialize(st, state); - AutoSuspendHelper.LatestBundle?.PutByteArray("__state", st.ToArray()); + if (AutoSuspendHelper.LatestBundle is null) + { + return Observable.Throw( + new InvalidOperationException("New bundle detected; no persisted state is available.")); + } + + var buffer = AutoSuspendHelper.LatestBundle.GetByteArray(StateKey); + if (buffer is null) + { + return Observable.Throw( + new InvalidOperationException("The persisted state buffer could not be found.")); + } + + return Observable.FromAsync(async () => + { + await using var stream = new MemoryStream(buffer, writable: false); + return await JsonSerializer.DeserializeAsync(stream, typeInfo).ConfigureAwait(false); + }); + } + catch (Exception ex) + { + return Observable.Throw(ex); + } + } + + /// + public IObservable SaveState(T state, JsonTypeInfo typeInfo) + { + ArgumentNullException.ThrowIfNull(typeInfo); + + try + { + using var stream = new MemoryStream(); + JsonSerializer.Serialize(stream, state, typeInfo); + + AutoSuspendHelper.LatestBundle?.PutByteArray(StateKey, stream.ToArray()); return Observables.Unit; } catch (Exception ex) @@ -65,16 +133,12 @@ public IObservable SaveState(object state) // TODO: Create Test } } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("InvalidateState uses JsonSerializer which requires dynamic code generation")] - [RequiresUnreferencedCode("InvalidateState uses JsonSerializer which may require unreferenced code")] -#endif - public IObservable InvalidateState() // TODO: Create Test + /// + public IObservable InvalidateState() { try { - AutoSuspendHelper.LatestBundle?.PutByteArray("__state", []); + AutoSuspendHelper.LatestBundle?.PutByteArray(StateKey, []); return Observables.Unit; } catch (Exception ex) diff --git a/src/ReactiveUI/Platforms/android/ControlFetcherMixin.cs b/src/ReactiveUI/Platforms/android/ControlFetcherMixin.cs index 43a6d79f7f..2f90bcb5c8 100644 --- a/src/ReactiveUI/Platforms/android/ControlFetcherMixin.cs +++ b/src/ReactiveUI/Platforms/android/ControlFetcherMixin.cs @@ -3,7 +3,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#nullable enable + using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; @@ -15,247 +18,531 @@ namespace ReactiveUI; /// -/// Control Fetcher Mix-in helps you automatically wire-up Activities and -/// Fragments via property names, similar to Butter Knife, as well as allows -/// you to fetch controls manually. +/// Control fetcher helpers for Android that support wiring up properties to Android resource IDs by name. /// +/// +/// +/// This API is intended for classic Android view wiring patterns (e.g., Activities/Fragments/Views). +/// It performs name-to-resource-id resolution using reflection over the generated Android Resource classes, +/// and caches lookups per assembly and per root view. +/// +/// +/// Trimming/AOT: resource discovery uses reflection over generated resource types and may require preserving +/// those members. See and related remarks. +/// +/// public static partial class ControlFetcherMixin { - private static readonly ConcurrentDictionary> _controlIds = new(); + /// + /// Cache mapping an assembly to a case-insensitive resource-name→id map. + /// + /// + /// This cache is populated on demand. The per-assembly map is immutable after creation to allow lock-free reads. + /// + private static readonly ConcurrentDictionary> ControlIds = new(); - private static readonly ConditionalWeakTable> _viewCache = []; + /// + /// Cache mapping a root view object to a per-property cached instance. + /// + /// + /// + /// This cache avoids repeated FindViewById calls for the same root and property name. + /// + /// + /// Threading: Android UI access is typically single-threaded; however, this cache uses a concurrent dictionary + /// to avoid race conditions if called from multiple threads (e.g., during tests or unusual scheduling). + /// + /// + private static readonly ConditionalWeakTable> ViewCache = new(); /// - /// Gets the control from an activity. + /// Cache of wire-up property lists per runtime type and resolve strategy. /// - /// The activity. - /// The property name. - /// The return view. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetControl uses reflection to find and set properties")] - [RequiresUnreferencedCode("GetControl may reference properties that could be trimmed")] -#endif - public static View? GetControl(this Activity activity, [CallerMemberName] string? propertyName = null) // TODO: Create Test - => GetCachedControl(propertyName, activity, () => activity.FindViewById(GetControlIdByName(activity.GetType().Assembly, propertyName))); + /// + /// This avoids repeated reflection over properties when wiring up controls multiple times. + /// + private static readonly ConcurrentDictionary<(Type Type, ResolveStrategy Strategy), PropertyInfo[]> WireUpMembersCache = new(); /// - /// Gets the control from a view. + /// Gets a control from an using the calling member name as the default resource name. /// - /// The view. - /// The assembly containing the user-defined view. - /// The property. - /// The return view. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetControl uses reflection to find and set properties")] - [RequiresUnreferencedCode("GetControl may reference properties that could be trimmed")] -#endif - public static View? GetControl(this View view, Assembly assembly, [CallerMemberName] string? propertyName = null) // TODO: Create Test - => GetCachedControl(propertyName, view, () => view.FindViewById(GetControlIdByName(assembly, propertyName))); + /// The activity that hosts the view hierarchy. + /// + /// The property name to use as the resource identifier. Defaults to the calling member name. + /// + /// The resolved view if found; otherwise . + [RequiresUnreferencedCode("Android resource discovery uses reflection over generated resource types that may be trimmed.")] + [RequiresDynamicCode("Android resource discovery uses reflection that may require dynamic code generation.")] + public static View? GetControl(this Activity activity, [CallerMemberName] string? propertyName = null) => + GetCachedControl( + propertyName, + activity, + static (root, name) => + { + var act = (Activity)root; + var id = GetControlIdByName(act.GetType().Assembly, name); + return act.FindViewById(id); + }); /// - /// Wires a control to a property. + /// Gets a control from an Android using the calling member name as the default resource name. /// - /// The layout view host. - /// The resolve members. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WireUpControls uses reflection to find and set properties")] - [RequiresUnreferencedCode("WireUpControls may reference properties that could be trimmed")] -#endif - public static void WireUpControls(this ILayoutViewHost layoutHost, ResolveStrategy resolveMembers = ResolveStrategy.Implicit) // TODO: Create Test + /// The root view. + /// The assembly containing the user-defined view and its resources. + /// + /// The property name to use as the resource identifier. Defaults to the calling member name. + /// + /// The resolved view if found; otherwise . + [RequiresUnreferencedCode("Android resource discovery uses reflection over generated resource types that may be trimmed.")] + [RequiresDynamicCode("Android resource discovery uses reflection that may require dynamic code generation.")] + public static View? GetControl(this View view, Assembly assembly, [CallerMemberName] string? propertyName = null) => + GetCachedControl( + propertyName, + view, + static (root, name, state) => + { + var v = (View)root; + var id = GetControlIdByName(state, name); + return v.FindViewById(id); + }, + assembly); + + /// + /// Wires view controls to properties on an . + /// + /// The layout host that exposes a . + /// The resolve strategy for selecting properties to wire. + /// Thrown when is null. + /// + /// Thrown when a property cannot be wired to a view with a corresponding resource identifier. + /// + [RequiresUnreferencedCode("WireUpControls uses reflection to discover properties and attributes that may be trimmed.")] + [RequiresDynamicCode("WireUpControls uses reflection that may require dynamic code generation.")] + public static void WireUpControls(this ILayoutViewHost layoutHost, ResolveStrategy resolveMembers = ResolveStrategy.Implicit) { ArgumentExceptionHelper.ThrowIfNull(layoutHost); - var members = layoutHost.GetWireUpMembers(resolveMembers).ToList(); - foreach (var member in members) + var hostType = layoutHost.GetType(); + var members = GetWireUpMembersCached(hostType, resolveMembers); + + for (var i = 0; i < members.Length; i++) { + var member = members[i]; + try { - var view = layoutHost.View?.GetControl(layoutHost.GetType().Assembly, member.GetResourceName()); - member.SetValue(layoutHost, view); + var root = layoutHost.View; + var resourceName = member.GetResourceName(); + + var resolved = root is null + ? null + : root.GetControl(hostType.Assembly, resourceName); + + member.SetValue(layoutHost, resolved); } catch (Exception ex) { - throw new - MissingFieldException("Failed to wire up the Property " + member.Name + " to a View in your layout with a corresponding identifier", ex); + throw new MissingFieldException( + "Failed to wire up the Property " + member.Name + + " to a View in your layout with a corresponding identifier.", + ex); } } } /// - /// Wires a control to a property. + /// Wires view controls to properties on an Android . /// - /// The view. - /// The resolve members. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WireUpControls uses reflection to find and set properties")] - [RequiresUnreferencedCode("WireUpControls may reference properties that could be trimmed")] -#endif - public static void WireUpControls(this View view, ResolveStrategy resolveMembers = ResolveStrategy.Implicit) // TODO: Create Test + /// The view whose properties should be wired. + /// The resolve strategy for selecting properties to wire. + /// Thrown when is null. + /// + /// Thrown when a property cannot be wired to a view with a corresponding resource identifier. + /// + [RequiresUnreferencedCode("WireUpControls uses reflection to discover properties and attributes that may be trimmed.")] + [RequiresDynamicCode("WireUpControls uses reflection that may require dynamic code generation.")] + public static void WireUpControls(this View view, ResolveStrategy resolveMembers = ResolveStrategy.Implicit) { ArgumentExceptionHelper.ThrowIfNull(view); - var members = view.GetWireUpMembers(resolveMembers); + var viewType = view.GetType(); + var members = GetWireUpMembersCached(viewType, resolveMembers); + var asm = viewType.Assembly; - foreach (var member in members) + for (var i = 0; i < members.Length; i++) { + var member = members[i]; + try { - // Find the android control with the same name - var currentView = view.GetControl(view.GetType().Assembly, member.GetResourceName()); - - // Set the activity field's value to the view with that identifier + var resourceName = member.GetResourceName(); + var currentView = view.GetControl(asm, resourceName); member.SetValue(view, currentView); } catch (Exception ex) { - throw new - MissingFieldException("Failed to wire up the Property " + member.Name + " to a View in your layout with a corresponding identifier", ex); + throw new MissingFieldException( + "Failed to wire up the Property " + member.Name + + " to a View in your layout with a corresponding identifier.", + ex); } } } /// - /// Wires a control to a property. - /// This should be called in the Fragment's OnCreateView, with the newly inflated layout. + /// Wires view controls to properties on an Android . /// - /// The fragment. - /// The inflated view. - /// The resolve members. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WireUpControls uses reflection to find and set properties")] - [RequiresUnreferencedCode("WireUpControls may reference properties that could be trimmed")] -#endif - public static void WireUpControls(this Fragment fragment, View inflatedView, ResolveStrategy resolveMembers = ResolveStrategy.Implicit) // TODO: Create Test + /// The fragment whose properties should be wired. + /// The inflated view returned from OnCreateView. + /// The resolve strategy for selecting properties to wire. + /// + /// Thrown when or is null. + /// + /// + /// Thrown when a property cannot be wired to a view with a corresponding resource identifier. + /// + [RequiresUnreferencedCode("WireUpControls uses reflection to discover properties and attributes that may be trimmed.")] + [RequiresDynamicCode("WireUpControls uses reflection that may require dynamic code generation.")] + public static void WireUpControls(this Fragment fragment, View inflatedView, ResolveStrategy resolveMembers = ResolveStrategy.Implicit) { ArgumentExceptionHelper.ThrowIfNull(fragment); + ArgumentExceptionHelper.ThrowIfNull(inflatedView); - var members = fragment.GetWireUpMembers(resolveMembers); + var fragmentType = fragment.GetType(); + var members = GetWireUpMembersCached(fragmentType, resolveMembers); + var asm = fragmentType.Assembly; - foreach (var member in members) + for (var i = 0; i < members.Length; i++) { + var member = members[i]; + try { - // Find the android control with the same name from the view - var view = inflatedView.GetControl(fragment.GetType().Assembly, member.GetResourceName()); - - // Set the activity field's value to the view with that identifier - member.SetValue(fragment, view); + var resourceName = member.GetResourceName(); + var resolved = inflatedView.GetControl(asm, resourceName); + member.SetValue(fragment, resolved); } catch (Exception ex) { - throw new - MissingFieldException("Failed to wire up the Property " + member.Name + " to a View in your layout with a corresponding identifier", ex); + throw new MissingFieldException( + "Failed to wire up the Property " + member.Name + + " to a View in your layout with a corresponding identifier.", + ex); } } } /// - /// Wires a control to a property. + /// Wires view controls to properties on an . /// - /// The Activity. - /// The resolve members. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WireUpControls uses reflection to find and set properties")] - [RequiresUnreferencedCode("WireUpControls may reference properties that could be trimmed")] -#endif - public static void WireUpControls(this Activity activity, ResolveStrategy resolveMembers = ResolveStrategy.Implicit) // TODO: Create Test + /// The activity whose properties should be wired. + /// The resolve strategy for selecting properties to wire. + /// Thrown when is null. + /// + /// Thrown when a property cannot be wired to a view with a corresponding resource identifier. + /// + [RequiresUnreferencedCode("WireUpControls uses reflection to discover properties and attributes that may be trimmed.")] + [RequiresDynamicCode("WireUpControls uses reflection that may require dynamic code generation.")] + public static void WireUpControls(this Activity activity, ResolveStrategy resolveMembers = ResolveStrategy.Implicit) { ArgumentExceptionHelper.ThrowIfNull(activity); - var members = activity.GetWireUpMembers(resolveMembers); + var activityType = activity.GetType(); + var members = GetWireUpMembersCached(activityType, resolveMembers); - foreach (var member in members) + for (var i = 0; i < members.Length; i++) { + var member = members[i]; + try { - // Find the android control with the same name - var view = activity.GetControl(member.GetResourceName()); - - // Set the activity field's value to the view with that identifier - member.SetValue(activity, view); + var resourceName = member.GetResourceName(); + var resolved = activity.GetControl(resourceName); + member.SetValue(activity, resolved); } catch (Exception ex) { - throw new - MissingFieldException("Failed to wire up the Property " + member.Name + " to a View in your layout with a corresponding identifier", ex); + throw new MissingFieldException( + "Failed to wire up the Property " + member.Name + + " to a View in your layout with a corresponding identifier.", + ex); } } } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetWireUpMembers uses reflection to analyze properties")] - [RequiresUnreferencedCode("GetWireUpMembers may reference properties that could be trimmed")] -#endif - internal static IEnumerable GetWireUpMembers(this object @this, ResolveStrategy resolveStrategy) + /// + /// Retrieves the set of properties on the specified object that are eligible for wire-up based on the provided + /// resolution strategy. + /// + /// This method uses reflection to discover properties, which may require dynamically generated + /// code and may be affected by code trimming. Use caution when calling this method in environments where reflection + /// or dynamic code generation is restricted. + /// The object whose properties are to be discovered for wire-up. + /// The strategy that determines which properties are considered for wire-up. + /// An enumerable collection of objects representing the properties eligible for wire-up. + /// The collection is empty if no matching properties are found. + [RequiresUnreferencedCode("Property discovery uses reflection and may require members removed by trimming.")] + [RequiresDynamicCode("Property discovery uses reflection that may require dynamic code generation.")] + internal static PropertyInfo[] GetWireUpMembers(this object @this, ResolveStrategy resolveStrategy) { - var members = @this.GetType().GetRuntimeProperties(); + var type = @this.GetType(); - return resolveStrategy switch - { - ResolveStrategy.ExplicitOptIn => - members.Where(static member => member.GetCustomAttribute(true) is not null), - ResolveStrategy.ExplicitOptOut => - members.Where(static member => typeof(View).IsAssignableFrom(member.PropertyType) && member.GetCustomAttribute(true) is null), - - // Implicit matches the Default. - _ => members.Where(static member => member.PropertyType.IsSubclassOf(typeof(View)) || member.GetCustomAttribute(true) is not null), - }; + return GetWireUpMembers(type, resolveStrategy); } + /// + /// Returns the set of properties eligible for wiring for a given runtime type and strategy. + /// + /// The runtime type. + /// The property selection strategy. + /// An array of properties eligible for wiring. + [RequiresUnreferencedCode("Property discovery uses reflection and may require members removed by trimming.")] + [RequiresDynamicCode("Property discovery uses reflection that may require dynamic code generation.")] + internal static PropertyInfo[] GetWireUpMembersCached(Type type, ResolveStrategy resolveStrategy) => + WireUpMembersCache.GetOrAdd((type, resolveStrategy), static key => + { + var members = key.Type.GetRuntimeProperties(); + + // Materialize once into a list then to array; no LINQ in per-wire loops. + var list = new List(); + + foreach (var member in members) + { + if (!member.CanWrite) + { + continue; + } + + switch (key.Strategy) + { + case ResolveStrategy.ExplicitOptIn: + if (member.GetCustomAttribute(inherit: true) is not null) + { + list.Add(member); + } + + break; + + case ResolveStrategy.ExplicitOptOut: + if (typeof(View).IsAssignableFrom(member.PropertyType) && + member.GetCustomAttribute(inherit: true) is null) + { + list.Add(member); + } + + break; + + default: + // Implicit: either a View-typed property or explicitly marked with WireUpResource. + if (member.PropertyType.IsSubclassOf(typeof(View)) || + member.GetCustomAttribute(inherit: true) is not null) + { + list.Add(member); + } + + break; + } + } + + return list.ToArray(); + }); + + /// + /// Gets the resource name for the specified property based on optional overrides. + /// + /// The property being wired. + /// The resource name to use. + [RequiresUnreferencedCode("Attribute lookup uses reflection and may require members removed by trimming.")] + [RequiresDynamicCode("Attribute lookup uses reflection that may require dynamic code generation.")] internal static string GetResourceName(this PropertyInfo member) { - var resourceNameOverride = member.GetCustomAttribute()?.ResourceNameOverride; - return resourceNameOverride ?? member.Name; + var attr = member.GetCustomAttribute(); + return attr?.ResourceNameOverride ?? member.Name; } - private static View? GetCachedControl(string? propertyName, object rootView, Func fetchControlFromView) + /// + /// Gets a cached control for a root view and property name, fetching it if absent. + /// + /// The cache key, typically the property name. + /// The root view object used as cache scope. + /// Factory used to fetch the view when not cached. + /// The cached view (possibly null). + private static View? GetCachedControl( + string? propertyName, + object rootView, + Func fetchControlFromView) { ArgumentExceptionHelper.ThrowIfNull(propertyName); - ArgumentExceptionHelper.ThrowIfNull(fetchControlFromView); - var ourViewCache = _viewCache.GetOrCreateValue(rootView); + var cache = ViewCache.GetOrCreateValue(rootView); - if (ourViewCache.TryGetValue(propertyName, out var ret)) + if (cache.TryGetValue(propertyName, out var existing)) { - return ret; + return existing; } - ret = fetchControlFromView(); + var created = fetchControlFromView(rootView, propertyName); - ourViewCache.Add(propertyName, ret); - return ret; + // ConcurrentDictionary indexer is safe; last write wins in a race. + cache[propertyName] = created; + return created; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetControlIdByName uses assembly reflection and type discovery")] - [RequiresUnreferencedCode("GetControlIdByName may reference types that could be trimmed")] -#endif - private static int GetControlIdByName(Assembly assembly, string? name) + /// + /// Gets a cached control for a root view and property name, fetching it if absent, with an extra state value. + /// + /// The type of state passed to the fetch function. + /// The cache key, typically the property name. + /// The root view object used as cache scope. + /// Factory used to fetch the view when not cached. + /// State passed to the fetch factory. + /// The cached view (possibly null). + private static View? GetCachedControl( + string? propertyName, + object rootView, + Func fetchControlFromView, + TState state) + { + ArgumentExceptionHelper.ThrowIfNull(propertyName); + ArgumentExceptionHelper.ThrowIfNull(fetchControlFromView); + + var cache = ViewCache.GetOrCreateValue(rootView); + + if (cache.TryGetValue(propertyName, out var existing)) + { + return existing; + } + + var created = fetchControlFromView(rootView, propertyName, state); + cache[propertyName] = created; + return created; + } + + /// + /// Resolves the Android resource ID for the given resource name within the specified assembly. + /// + /// The assembly whose generated resource types should be inspected. + /// The resource name. + /// The resolved integer resource ID. + /// Thrown when is null or empty. + /// Thrown when the name cannot be resolved to an ID. + [RequiresUnreferencedCode("Android resource discovery uses reflection over generated resource types that may be trimmed.")] + [RequiresDynamicCode("Android resource discovery uses reflection that may require dynamic code generation.")] + private static int GetControlIdByName(Assembly assembly, string name) { - ArgumentExceptionHelper.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Resource name must not be null or whitespace.", nameof(name)); + } + + var ids = ControlIds.GetOrAdd(assembly, static asm => BuildIdMap(asm)); + + if (ids.TryGetValue(name, out var id)) + { + return id; + } + + throw new MissingFieldException($"No Android resource id named '{name}' was found for assembly '{assembly.FullName}'."); + } - var ids = _controlIds.GetOrAdd( - assembly, - static currentAssembly => - { + /// + /// Builds an immutable mapping of resource name to integer ID for an assembly. + /// + /// The assembly to inspect. + /// A case-insensitive mapping of resource name to ID. + [RequiresUnreferencedCode("Android resource discovery uses reflection over generated resource types that may be trimmed.")] + [RequiresDynamicCode("Android resource discovery uses reflection that may require dynamic code generation.")] + private static IReadOnlyDictionary BuildIdMap(Assembly assembly) + { #if NET8_0_OR_GREATER - var resources = Assembly.Load(currentAssembly - .GetReferencedAssemblies() - .First(static an => an.FullName.StartsWith("_Microsoft.Android.Resource.Designer")).ToString()) - .GetModules() - .SelectMany(static x => x.GetTypes()) - .First(static x => x.Name == "ResourceConstant"); + // Android .NET 8+ generates a resource designer in a referenced assembly. + var referenced = assembly.GetReferencedAssemblies(); + AssemblyName? designerName = null; + + for (var i = 0; i < referenced.Length; i++) + { + var an = referenced[i]; + if (an.FullName is not null && an.FullName.StartsWith("_Microsoft.Android.Resource.Designer", StringComparison.Ordinal)) + { + designerName = an; + break; + } + } + + if (designerName is null) + { + throw new InvalidOperationException("Could not locate the Android resource designer assembly."); + } + + var resourcesAssembly = Assembly.Load(designerName); + var modules = resourcesAssembly.GetModules(); + + Type? resources = null; + for (var i = 0; i < modules.Length && resources is null; i++) + { + var types = modules[i].GetTypes(); + for (var j = 0; j < types.Length; j++) + { + if (types[j].Name == "ResourceConstant") + { + resources = types[j]; + break; + } + } + } + + if (resources is null) + { + throw new InvalidOperationException("Could not locate generated resource type 'ResourceConstant'."); + } #else - var resources = currentAssembly.GetModules().SelectMany(x => x.GetTypes()).First(x => x.Name == "Resource"); + var modules = assembly.GetModules(); + Type? resources = null; + + for (var i = 0; i < modules.Length && resources is null; i++) + { + var types = modules[i].GetTypes(); + for (var j = 0; j < types.Length; j++) + { + if (types[j].Name == "Resource") + { + resources = types[j]; + break; + } + } + } + + if (resources is null) + { + throw new InvalidOperationException("Could not locate generated resource type 'Resource'."); + } #endif - var idType = resources.GetNestedType("Id") ?? throw new InvalidOperationException("Id is not a valid nested type in the resources."); - return idType.GetFields() - .Where(static x => x.FieldType == typeof(int)) - .ToDictionary(static k => k.Name, static v => ((int?)v.GetRawConstantValue()) ?? 0, StringComparer.InvariantCultureIgnoreCase); - }); + var idType = resources.GetNestedType("Id"); + if (idType is null) + { + throw new InvalidOperationException("Id is not a valid nested type in the generated resources."); + } + + var fields = idType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + var dict = new Dictionary(fields.Length, StringComparer.InvariantCultureIgnoreCase); + + for (var i = 0; i < fields.Length; i++) + { + var f = fields[i]; + if (f.FieldType != typeof(int)) + { + continue; + } + + // Generated constants use raw constant values. + if (f.GetRawConstantValue() is int value) + { + dict[f.Name] = value; + } + } - return ids[name]; + return dict; } } diff --git a/src/ReactiveUI/Platforms/android/FlexibleCommandBinder.cs b/src/ReactiveUI/Platforms/android/FlexibleCommandBinder.cs index 95ab1dd1e9..5fb76292ca 100644 --- a/src/ReactiveUI/Platforms/android/FlexibleCommandBinder.cs +++ b/src/ReactiveUI/Platforms/android/FlexibleCommandBinder.cs @@ -6,8 +6,6 @@ using System.Reflection; using System.Windows.Input; -using ReactiveUI.Helpers; - namespace ReactiveUI; /// @@ -21,39 +19,7 @@ public abstract class FlexibleCommandBinder : ICreatesCommandBinding private readonly Dictionary _config = []; /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses Reflection.GetValueSetterForProperty which requires dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses Reflection.GetValueSetterForProperty which may require unreferenced code")] -#endif - public int GetAffinityForObject(Type type, bool hasEventTarget) - { - if (hasEventTarget) - { - return 0; - } - - var match = _config.Keys - .Where(x => x.IsAssignableFrom(type)) - .OrderByDescending(x => _config[x].Affinity) - .FirstOrDefault(); - - if (match is null) - { - return 0; - } - - var typeProperties = _config[match]; - return typeProperties.Affinity; - } - - /// -#if NET6_0_OR_GREATER - public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>( - bool hasEventTarget) -#else - public int GetAffinityForObject( - bool hasEventTarget) -#endif + public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>(bool hasEventTarget) { if (hasEventTarget) { @@ -75,11 +41,9 @@ public int GetAffinityForObject( } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindCommandToObject uses Reflection.GetValueSetterForProperty which requires dynamic code generation")] - [RequiresUnreferencedCode("BindCommandToObject uses Reflection.GetValueSetterForProperty which may require unreferenced code")] -#endif - public IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter) + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(ICommand? command, T? target, IObservable commandParameter) + where T : class { ArgumentExceptionHelper.ThrowIfNull(target); @@ -95,13 +59,100 @@ public IDisposable BindCommandToObject(ICommand? command, object? target, IObser } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindCommandToObject uses Reflection.GetValueSetterForProperty which requires dynamic code generation")] - [RequiresUnreferencedCode("BindCommandToObject uses Reflection.GetValueSetterForProperty which may require unreferenced code")] -#endif - public IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter, string eventName) + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject(ICommand? command, T? target, IObservable commandParameter, string eventName) + where T : class + => Disposable.Empty; + + /// + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>( + ICommand? command, + T? target, + IObservable commandParameter, + Action> addHandler, + Action> removeHandler) + where T : class where TEventArgs : EventArgs - => throw new NotImplementedException(); + { + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(addHandler); + ArgumentNullException.ThrowIfNull(removeHandler); + + // Match existing binder behavior: if there is no command, create a no-op binding. + if (command is null) + { + return Disposable.Empty; + } + + // Keep the existing "null means use target" idiom used by ForEvent. + commandParameter ??= Observable.Return((object?)target); + + // The latest parameter may be updated from a different thread than the event thread. + object? latestParam = null; + + // Stable handler for deterministic unsubscription. + void Handler(object? sender, TEventArgs e) + { + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } + + // Subscribe to parameter updates first, then attach the event handler. + var parameterSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); + addHandler(Handler); + + // If we can locate a conventional enabled property on the runtime target, keep it in sync with CanExecute. + // This is intentionally best-effort and does not throw if the property is absent or cannot be set. + Action? enabledSetter = null; + try + { + // Common Android idiom: "Enabled" boolean property. + // Use runtime type so derived types are supported. + var enabledProp = typeof(T).GetRuntimeProperty("Enabled"); + if (enabledProp is not null) + { + enabledSetter = Reflection.GetValueSetterForProperty(enabledProp); + } + } + catch + { + // Best-effort only; ignore reflection failures. + enabledSetter = null; + } + + IDisposable? canExecuteSub = null; + if (enabledSetter is not null) + { + // Initial enabled state (default parameter is null until the first commandParameter emission). + enabledSetter(target, command.CanExecute(Volatile.Read(ref latestParam)), null); + + // Keep Enabled in sync with CanExecuteChanged. + canExecuteSub = Observable.FromEvent( + eventHandler => + { + void CanExecuteHandler(object? s, EventArgs e) => + eventHandler(command.CanExecute(Volatile.Read(ref latestParam))); + return CanExecuteHandler; + }, + h => command.CanExecuteChanged += h, + h => command.CanExecuteChanged -= h) + .Subscribe(x => enabledSetter(target, x, null)); + } + + // Dispose ordering: detach event handler and CanExecute subscription after stopping parameter updates. + // The handler instance is stable, so Remove is correct. + return canExecuteSub is null + ? new CompositeDisposable( + parameterSub, + Disposable.Create(() => removeHandler(Handler))) + : new CompositeDisposable( + parameterSub, + canExecuteSub, + Disposable.Create(() => removeHandler(Handler))); + } /// /// Creates a commands binding from event and a property. @@ -112,9 +163,7 @@ public IDisposable BindCommandToObject(ICommand? command, object? ta /// Command parameter. /// Event name. /// Enabled property name. -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This member uses reflection to discover event members and associated delegate types.")] -#endif + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] protected static IDisposable ForEvent(ICommand? command, object? target, IObservable commandParameter, string eventName, PropertyInfo enabledProperty) { ArgumentExceptionHelper.ThrowIfNull(command); @@ -155,6 +204,168 @@ protected static IDisposable ForEvent(ICommand? command, object? target, IObserv .Subscribe(x => enabledSetter(target, x, null))); } + /// + /// Creates a command binding from an event using explicit add/remove handler delegates and optionally + /// synchronizes an enabled property with . + /// + /// The event arguments type. + /// The command to bind. + /// The event source object. + /// Observable producing command parameter values. If , is used. + /// Adds the handler to the event source. + /// Removes the handler from the event source. + /// + /// Optional setter for an enabled-like property. If , enabled synchronization is skipped. + /// The setter is expected to accept as the first argument and a value as the second. + /// + /// A disposable that unbinds the command and stops enabled synchronization. + /// + /// Thrown when is . + /// + protected static IDisposable ForEvent( + ICommand? command, + object? target, + IObservable? commandParameter, + Action> addHandler, + Action> removeHandler, + Action? enabledSetter) + where TEventArgs : EventArgs + { + ArgumentExceptionHelper.ThrowIfNull(command); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(addHandler); + ArgumentNullException.ThrowIfNull(removeHandler); + + // Preserve existing idiom: null commandParameter means use target. + commandParameter ??= Observable.Return(target); + + object? latestParam = null; + + // Stable handler for deterministic unsubscription. + void Handler(object? sender, TEventArgs e) + { + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } + + // Subscribe to parameter updates first so the first event sees the latest parameter. + var parameterSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); + + // Hook the event without reflection. + addHandler(Handler); + + // If there is no enabled setter, we're done. + if (enabledSetter is null) + { + return new CompositeDisposable( + parameterSub, + Disposable.Create(() => removeHandler(Handler))); + } + + // Initial enabled state. + enabledSetter(target, command.CanExecute(Volatile.Read(ref latestParam)), null); + + // Keep enabled state in sync with CanExecuteChanged. + var canExecuteSub = Observable.FromEvent( + eventHandler => + { + void CanExecuteHandler(object? s, EventArgs e) => + eventHandler(command.CanExecute(Volatile.Read(ref latestParam))); + return CanExecuteHandler; + }, + h => command.CanExecuteChanged += h, + h => command.CanExecuteChanged -= h) + .Subscribe(x => enabledSetter(target, x, null)); + + return new CompositeDisposable( + parameterSub, + canExecuteSub, + Disposable.Create(() => removeHandler(Handler))); + } + + /// + /// Creates a command binding from an event using explicit add/remove handler delegates and optionally + /// synchronizes an enabled property with . + /// + /// The command to bind. + /// The event source object. + /// Observable producing command parameter values. If , is used. + /// Adds the handler to the event source. + /// Removes the handler from the event source. + /// + /// Optional setter for an enabled-like property. If , enabled synchronization is skipped. + /// The setter is expected to accept as the first argument and a value as the second. + /// + /// A disposable that unbinds the command and stops enabled synchronization. + /// + /// Thrown when is . + /// + protected static IDisposable ForEvent( + ICommand? command, + object? target, + IObservable? commandParameter, + Action addHandler, + Action removeHandler, + Action? enabledSetter) + { + ArgumentExceptionHelper.ThrowIfNull(command); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(addHandler); + ArgumentNullException.ThrowIfNull(removeHandler); + + // Preserve existing idiom: null commandParameter means use target. + commandParameter ??= Observable.Return(target); + + object? latestParam = null; + + // Stable handler for deterministic unsubscription. + void Handler(object? sender, EventArgs e) + { + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } + + // Subscribe to parameter updates first so the first event sees the latest parameter. + var parameterSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); + + // Hook the event without reflection. + addHandler(Handler); + + // If there is no enabled setter, we're done. + if (enabledSetter is null) + { + return new CompositeDisposable( + parameterSub, + Disposable.Create(() => removeHandler(Handler))); + } + + // Initial enabled state. + enabledSetter(target, command.CanExecute(Volatile.Read(ref latestParam)), null); + + // Keep enabled state in sync with CanExecuteChanged. + var canExecuteSub = Observable.FromEvent( + eventHandler => + { + void CanExecuteHandler(object? s, EventArgs e) => + eventHandler(command.CanExecute(Volatile.Read(ref latestParam))); + return CanExecuteHandler; + }, + h => command.CanExecuteChanged += h, + h => command.CanExecuteChanged -= h) + .Subscribe(x => enabledSetter(target, x, null)); + + return new CompositeDisposable( + parameterSub, + canExecuteSub, + Disposable.Create(() => removeHandler(Handler))); + } + /// /// Registers an observable factory for the specified type and property. /// diff --git a/src/ReactiveUI/Platforms/android/LayoutViewHost.cs b/src/ReactiveUI/Platforms/android/LayoutViewHost.cs index 34743210cf..e69151f1a6 100644 --- a/src/ReactiveUI/Platforms/android/LayoutViewHost.cs +++ b/src/ReactiveUI/Platforms/android/LayoutViewHost.cs @@ -6,11 +6,20 @@ using Android.Content; using Android.Views; +using static ReactiveUI.ControlFetcherMixin; + namespace ReactiveUI; /// -/// A class that implements the Android ViewHolder pattern. Use it along -/// with GetViewHost. +/// Base class implementing the Android ViewHolder pattern. +/// +/// owns a single inflated instance and +/// optionally wires child controls to properties on the host. +/// +/// +/// This type provides both AOT-safe construction paths and a legacy reflection-based +/// auto-wireup path for compatibility. +/// /// public abstract class LayoutViewHost : ILayoutViewHost, IEnableLogger { @@ -19,59 +28,180 @@ public abstract class LayoutViewHost : ILayoutViewHost, IEnableLogger /// /// Initializes a new instance of the class. /// + /// + /// This constructor performs no inflation or wiring and exists to support + /// derived types that manage view creation manually. + /// protected LayoutViewHost() { } + /// + /// Initializes a new instance of the class by inflating + /// a layout resource. + /// + /// The Android context. + /// The layout resource identifier. + /// The parent view group. + /// Whether to attach the inflated view to the parent. + /// + /// + /// This constructor is fully AOT- and trimming-safe. + /// + /// + /// No automatic control wiring is performed. Consumers are expected to + /// wire controls explicitly. + /// + /// + protected LayoutViewHost( + Context context, + int layoutId, + ViewGroup parent, + bool attachToRoot) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(parent); + + View = Inflate(context, layoutId, parent, attachToRoot); + } + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class by inflating + /// a layout resource and invoking an explicit, AOT-safe binder. /// - /// The CTX. - /// The layout identifier. - /// The parent. - /// if set to true [attach to root]. - /// if set to true [perform automatic wire-up]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif - protected LayoutViewHost(Context ctx, int layoutId, ViewGroup parent, bool attachToRoot = false, bool performAutoWireup = true) + /// The Android context. + /// The layout resource identifier. + /// The parent view group. + /// Whether to attach the inflated view to the parent. + /// + /// A callback responsible for explicitly wiring child views to the host. + /// + /// + /// + /// This constructor is fully AOT-safe and avoids reflection entirely. + /// + /// + /// The callback is invoked only after has been assigned. + /// + /// + protected LayoutViewHost( + Context context, + int layoutId, + ViewGroup parent, + bool attachToRoot, + Action bind) + : this(context, layoutId, parent, attachToRoot) { - var inflater = LayoutInflater.FromContext(ctx); + ArgumentNullException.ThrowIfNull(bind); - if (inflater is null) + if (View is not null) { - return; + bind(this, View); } + } - View = inflater.Inflate(layoutId, parent, attachToRoot); + /// + /// Initializes a new instance of the class by inflating + /// a layout resource and optionally performing reflection-based auto-wireup. + /// + /// The Android context. + /// The layout resource identifier. + /// The parent view group. + /// Whether to attach the inflated view to the parent. + /// + /// If , performs automatic wiring using reflection. + /// + /// + /// The member resolution strategy used during auto-wireup. + /// + /// + /// + /// This constructor is not trimming- or AOT-safe when auto-wireup is enabled. + /// + /// + /// It exists for backward compatibility and should be avoided in new code. + /// + /// + [RequiresUnreferencedCode("Auto wire-up uses reflection and member discovery.")] + [RequiresDynamicCode("Auto wire-up relies on runtime type inspection.")] + protected LayoutViewHost( + Context context, + int layoutId, + ViewGroup parent, + bool attachToRoot, + bool performAutoWireup, + ResolveStrategy resolveStrategy) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(parent); + + View = Inflate(context, layoutId, parent, attachToRoot); if (performAutoWireup) { - this.WireUpControls(); + this.WireUpControls(resolveStrategy); } } - /// + /// public View? View { get => _view; - set { - if (_view == value) + if (ReferenceEquals(_view, value)) { return; } _view = value; + + // Associate the host with the view for retrieval via ViewMixins. _view?.SetTag(ViewMixins.ViewHostTag, this.ToJavaObject()); } } /// - /// Casts the LayoutViewHost to a View. + /// Implicitly converts a to its backing . + /// + /// The host instance. + public static implicit operator View?(LayoutViewHost host) + { + ArgumentExceptionHelper.ThrowIfNull(host); + return host._view; + } + + /// + /// Inflates an Android layout resource into a using the provided context. /// - /// The LayoutViewHost to cast. - public static implicit operator View?(LayoutViewHost layoutViewHost) => layoutViewHost?.View; + /// The Android context used to obtain a . + /// The layout resource identifier to inflate. + /// + /// The parent view group to associate with the inflated view during inflation. + /// This parameter may influence layout parameters even when is . + /// + /// + /// Whether the inflated view should be attached to during inflation. + /// + /// + /// The inflated instance. + /// + /// + /// Thrown when a cannot be obtained from the provided . + /// + /// + /// + /// This method centralizes layout inflation to avoid duplication across constructors and + /// to ensure consistent error handling. + /// + /// + /// The method performs no reflection and is fully compatible with AOT and trimming. + /// + /// + private static View Inflate(Context context, int layoutId, ViewGroup parent, bool attachToRoot) + { + var inflater = LayoutInflater.FromContext(context); + return inflater?.Inflate(layoutId, parent, attachToRoot) + ?? throw new InvalidOperationException("LayoutInflater could not be obtained from context."); + } } diff --git a/src/ReactiveUI/Platforms/android/PlatformRegistrations.cs b/src/ReactiveUI/Platforms/android/PlatformRegistrations.cs index ff7236a467..8059b624c0 100644 --- a/src/ReactiveUI/Platforms/android/PlatformRegistrations.cs +++ b/src/ReactiveUI/Platforms/android/PlatformRegistrations.cs @@ -3,8 +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 ReactiveUI.Helpers; - namespace ReactiveUI; /// @@ -14,18 +12,14 @@ namespace ReactiveUI; public class PlatformRegistrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [SuppressMessage("Trimming", "IL2046:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Not using reflection")] - [SuppressMessage("AOT", "IL3051:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Not using reflection")] -#endif - public void Register(Action, Type> registerFunction) // TODO: Create Test + public void Register(IRegistrar registrar) // TODO: Create Test { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); - registerFunction(static () => new PlatformOperations(), typeof(IPlatformOperations)); - registerFunction(static () => new ComponentModelTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new AndroidObservableForWidgets(), typeof(ICreatesObservableForProperty)); - registerFunction(static () => new AndroidCommandBinders(), typeof(ICreatesCommandBinding)); + registrar.RegisterConstant(static () => new PlatformOperations()); + registrar.RegisterConstant(static () => new ComponentModelFallbackConverter()); + registrar.RegisterConstant(static () => new AndroidObservableForWidgets()); + registrar.RegisterConstant(static () => new AndroidCommandBinders()); if (!ModeDetector.InUnitTestRunner()) { @@ -33,6 +27,6 @@ public void Register(Action, Type> registerFunction) // TODO: Creat RxSchedulers.MainThreadScheduler = HandlerScheduler.MainThreadScheduler; } - registerFunction(static () => new BundleSuspensionDriver(), typeof(ISuspensionDriver)); + registrar.RegisterConstant(static () => new BundleSuspensionDriver()); } } diff --git a/src/ReactiveUI/Platforms/android/ReactiveActivity.cs b/src/ReactiveUI/Platforms/android/ReactiveActivity.cs index 425ca0fbc4..8cead871e4 100644 --- a/src/ReactiveUI/Platforms/android/ReactiveActivity.cs +++ b/src/ReactiveUI/Platforms/android/ReactiveActivity.cs @@ -13,10 +13,6 @@ namespace ReactiveUI; /// This is an Activity that is both an Activity and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveActivity inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveActivity inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveActivity : Activity, IReactiveObject, IReactiveNotifyPropertyChanged, IHandleObservableErrors { private readonly Subject _activated = new(); diff --git a/src/ReactiveUI/Platforms/android/ReactiveActivity{TViewModel}.cs b/src/ReactiveUI/Platforms/android/ReactiveActivity{TViewModel}.cs index 117009809b..736d51705b 100644 --- a/src/ReactiveUI/Platforms/android/ReactiveActivity{TViewModel}.cs +++ b/src/ReactiveUI/Platforms/android/ReactiveActivity{TViewModel}.cs @@ -12,10 +12,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// /// The view model type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveActivity inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveActivity inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveActivity : ReactiveActivity, IViewFor, ICanActivate where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/android/ReactiveFragment.cs b/src/ReactiveUI/Platforms/android/ReactiveFragment.cs index 3d16ca3228..22762289c6 100644 --- a/src/ReactiveUI/Platforms/android/ReactiveFragment.cs +++ b/src/ReactiveUI/Platforms/android/ReactiveFragment.cs @@ -14,10 +14,6 @@ namespace ReactiveUI; /// This is a Fragment that is both an Activity and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveFragment inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveFragment inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveFragment : Fragment, IReactiveNotifyPropertyChanged, IReactiveObject, IHandleObservableErrors { private readonly Subject _activated = new(); diff --git a/src/ReactiveUI/Platforms/android/ReactiveFragment{TViewModel}.cs b/src/ReactiveUI/Platforms/android/ReactiveFragment{TViewModel}.cs index a10004baac..a6bed33e27 100644 --- a/src/ReactiveUI/Platforms/android/ReactiveFragment{TViewModel}.cs +++ b/src/ReactiveUI/Platforms/android/ReactiveFragment{TViewModel}.cs @@ -14,10 +14,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// /// The view model type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveFragment inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveFragment inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveFragment : ReactiveFragment, IViewFor, ICanActivate where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/android/ReactiveViewHost.cs b/src/ReactiveUI/Platforms/android/ReactiveViewHost.cs index a9d7d0e573..bf38052057 100644 --- a/src/ReactiveUI/Platforms/android/ReactiveViewHost.cs +++ b/src/ReactiveUI/Platforms/android/ReactiveViewHost.cs @@ -3,60 +3,147 @@ // 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.Reflection; +using System.Runtime.Serialization; using Android.Content; using Android.Views; +using static ReactiveUI.ControlFetcherMixin; + namespace ReactiveUI; /// -/// A class that implements the Android ViewHolder pattern with a -/// ViewModel. Use it along with GetViewHost. +/// A class that implements the Android ViewHolder pattern with a ViewModel. +/// Use it along with GetViewHost. /// /// The view model type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveViewHost uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveViewHost uses methods that may require unreferenced code")] -#endif -public abstract class ReactiveViewHost : LayoutViewHost, IViewFor, IReactiveNotifyPropertyChanged>, IReactiveObject +/// +/// +/// Trimming/AOT: Prefer constructors that do not enable legacy auto-wireup. These paths avoid reflection and do not +/// allocate property metadata. +/// +/// +/// Compatibility: A legacy constructor is provided that enables reflection-based wiring and initializes +/// for older infrastructure. +/// +/// +public abstract class ReactiveViewHost : + LayoutViewHost, + IViewFor, + IReactiveNotifyPropertyChanged>, + IReactiveObject where TViewModel : class, IReactiveObject { /// /// All public properties. /// + /// + /// This field is used by legacy reflection-based wiring. It is not initialized by default in AOT-safe construction + /// paths to avoid reflection and allocations. If a derived type requires this, use the legacy constructor. + /// [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401: Field should be private", Justification = "Legacy reasons")] [SuppressMessage("Design", "CA1051: Do not declare visible instance fields", Justification = "Legacy reasons")] [IgnoreDataMember] [JsonIgnore] protected Lazy? allPublicProperties; + /// + /// Backing field for . + /// private TViewModel? _viewModel; /// /// Initializes a new instance of the class. /// - /// The CTX. - /// The layout identifier. - /// The parent. - /// if set to true [attach to root]. - /// if set to true [perform automatic wire-up]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReactiveViewHost uses methods that may require unreferenced code")] -#endif - protected ReactiveViewHost(Context ctx, int layoutId, ViewGroup parent, bool attachToRoot = false, bool performAutoWireup = true) - : base(ctx, layoutId, parent, attachToRoot, performAutoWireup) => - SetupRxObj(); + /// + /// This constructor performs no inflation or wiring and is AOT-safe. + /// Derived types may assign manually. + /// + protected ReactiveViewHost() + { + SetupRxObjAot(); + } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class by inflating a layout resource. + /// + /// The Android context. + /// The layout resource identifier. + /// The parent view group. + /// Whether to attach the inflated view to the parent. + /// + /// This constructor is fully AOT- and trimming-safe and performs no reflection-based auto-wireup. + /// + protected ReactiveViewHost(Context ctx, int layoutId, ViewGroup parent, bool attachToRoot = false) + : base(ctx, layoutId, parent, attachToRoot) + { + SetupRxObjAot(); + } + + /// + /// Initializes a new instance of the class by inflating a layout resource + /// and invoking an explicit, AOT-safe binder callback. + /// + /// The Android context. + /// The layout resource identifier. + /// The parent view group. + /// Whether to attach the inflated view to the parent. + /// + /// A callback responsible for explicitly wiring child views to the host. + /// + /// + /// This constructor is fully AOT-safe and avoids reflection entirely. + /// + /// Thrown when is . + protected ReactiveViewHost(Context ctx, int layoutId, ViewGroup parent, bool attachToRoot, Action, View> bind) + : base( + ctx, + layoutId, + parent, + attachToRoot, + (host, view) => + { + // The base constructor guarantees 'host' is the derived instance. + bind((ReactiveViewHost)host, view); + }) + { + ArgumentNullException.ThrowIfNull(bind); + SetupRxObjAot(); + } + + /// + /// Initializes a new instance of the class by inflating a layout resource + /// and optionally performing reflection-based auto-wireup. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReactiveViewHost uses methods that may require unreferenced code")] -#endif - protected ReactiveViewHost() => SetupRxObj(); + /// The Android context. + /// The layout resource identifier. + /// The parent view group. + /// Whether to attach the inflated view to the parent. + /// + /// If , performs automatic wiring using reflection. + /// + /// + /// The member resolution strategy used during auto-wireup. + /// + /// + /// This constructor exists for backward compatibility and is not trimming/AOT safe when + /// is . + /// + [RequiresUnreferencedCode("Legacy auto-wireup uses reflection and member discovery.")] + [RequiresDynamicCode("Legacy auto-wireup relies on runtime type inspection.")] + protected ReactiveViewHost( + Context ctx, + int layoutId, + ViewGroup parent, + bool attachToRoot, + bool performAutoWireup, + ResolveStrategy resolveStrategy) + : base(ctx, layoutId, parent, attachToRoot, performAutoWireup, resolveStrategy) + { + SetupRxObjLegacyReflection(); + } /// public event PropertyChangedEventHandler? PropertyChanged; @@ -75,7 +162,7 @@ public TViewModel? ViewModel object? IViewFor.ViewModel { get => _viewModel; - set => _viewModel = (TViewModel?)value!; + set => ViewModel = (TViewModel?)value; } /// @@ -89,34 +176,24 @@ public TViewModel? ViewModel public IObservable>> Changed => this.GetChangedObservable(); /// - /// Gets the thrown exceptions. + /// Gets an observable of exceptions thrown during reactive operations on this instance. /// [IgnoreDataMember] [JsonIgnore] public IObservable ThrownExceptions => this.GetThrownExceptionsObservable(); /// - /// When this method is called, an object will not fire change - /// notifications (neither traditional nor Observable notifications) + /// When this method is called, an object will not fire change notifications (neither traditional nor observable) /// until the return value is disposed. /// - /// An object that, when disposed, reenables change - /// notifications. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SuppressChangeNotifications uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SuppressChangeNotifications uses methods that may require unreferenced code")] -#endif - public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this); // TODO: Create Test + /// An that re-enables change notifications when disposed. + public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this); /// - /// Gets a value indicating if change notifications are enabled. + /// Gets a value indicating whether change notifications are enabled. /// - /// A value indicating if change notifications are on or off. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("AreChangeNotificationsEnabled uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("AreChangeNotificationsEnabled uses methods that may require unreferenced code")] -#endif - public bool AreChangeNotificationsEnabled() => IReactiveObjectExtensions.AreChangeNotificationsEnabled(this); // TODO: Create Test + /// if change notifications are enabled; otherwise, . + public bool AreChangeNotificationsEnabled() => IReactiveObjectExtensions.AreChangeNotificationsEnabled(this); /// void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args); @@ -124,18 +201,36 @@ public TViewModel? ViewModel /// void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args); + /// + /// Reinitializes reactive infrastructure after deserialization. + /// + /// The streaming context. [OnDeserialized] -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SetupRxObj uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SetupRxObj uses methods that may require unreferenced code")] -#endif - private void SetupRxObj(in StreamingContext sc) => SetupRxObj(); - -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SetupRxObj uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SetupRxObj uses methods that may require unreferenced code")] -#endif - private void SetupRxObj() => - allPublicProperties = new Lazy(() => - [.. GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)]); + private void SetupRxObj(in StreamingContext sc) => SetupRxObjAot(); + + /// + /// Initializes the instance for AOT-safe operation. + /// + /// + /// This method intentionally does not touch to avoid reflection and allocations. + /// + private void SetupRxObjAot() + { + // No reflection-based property caching in AOT-safe paths. + allPublicProperties = null; + } + + /// + /// Initializes legacy reflection metadata used by older auto-wireup infrastructure. + /// + /// + /// This allocates reflection metadata and is not trimming/AOT safe. + /// + [RequiresUnreferencedCode("This method uses reflection to enumerate public instance properties.")] + [RequiresDynamicCode("This method uses reflection to enumerate public instance properties.")] + private void SetupRxObjLegacyReflection() + { + allPublicProperties = new Lazy( + () => GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)); + } } diff --git a/src/ReactiveUI/Platforms/apple-common/AppSupportJsonSuspensionDriver.cs b/src/ReactiveUI/Platforms/apple-common/AppSupportJsonSuspensionDriver.cs index 576662d6ad..d617b6107b 100644 --- a/src/ReactiveUI/Platforms/apple-common/AppSupportJsonSuspensionDriver.cs +++ b/src/ReactiveUI/Platforms/apple-common/AppSupportJsonSuspensionDriver.cs @@ -4,58 +4,140 @@ // See the LICENSE file in the project root for full license information. using System.IO; +using System.Runtime.CompilerServices; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using Foundation; namespace ReactiveUI; /// -/// Loads and saves state to persistent storage. +/// Loads and saves state to persistent storage under the platform Application Support directory. /// -public class AppSupportJsonSuspensionDriver : ISuspensionDriver +/// +/// +/// This driver supports two serialization modes: +/// +/// +/// +/// +/// Source-generated System.Text.Json via overloads accepting . This is trimming/AOT-friendly. +/// +/// +/// +/// +/// Reflection-based System.Text.Json via interface methods. These are marked with +/// and . +/// +/// +/// +/// +/// The persisted file name is state.dat. +/// +/// +public sealed class AppSupportJsonSuspensionDriver : ISuspensionDriver { - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("LoadState implementations may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("LoadState implementations may use serialization which may require unreferenced code")] -#endif - public IObservable LoadState() + /// + /// The default subdirectory used beneath Application Support. + /// + private const string DefaultSubDirectory = "Data"; + + /// + /// The persisted state file name. + /// + private const string StateFileName = "state.dat"; + + /// + /// Lazily computed directory path used to reduce repeated Foundation calls and filesystem checks. + /// + private readonly Lazy _appDirectory; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The application-specific subdirectory beneath Application Support to store state. Defaults to Data. + /// + /// Thrown when is . + public AppSupportJsonSuspensionDriver(string subDirectory = DefaultSubDirectory) + { + ArgumentNullException.ThrowIfNull(subDirectory); + + _appDirectory = new Lazy( + () => CreateAppDirectory(NSSearchPathDirectory.ApplicationSupportDirectory, subDirectory), + isThreadSafe: true); + } + + /// + public IObservable LoadState(JsonTypeInfo typeInfo) { + ArgumentNullException.ThrowIfNull(typeInfo); + try { - var target = Path.Combine(CreateAppDirectory(NSSearchPathDirectory.ApplicationSupportDirectory), "state.dat"); - - var result = default(object); - using (var st = File.OpenRead(target)) - { - result = JsonSerializer.Deserialize(st); - } + var path = GetStatePath(); + using var stream = File.OpenRead(path); + var result = JsonSerializer.Deserialize(stream, typeInfo); return Observable.Return(result); } catch (Exception ex) { - return Observable.Throw(ex); + return Observable.Throw(ex); + } + } + + /// + public IObservable SaveState(T state, JsonTypeInfo typeInfo) + { + ArgumentNullException.ThrowIfNull(typeInfo); + + try + { + var path = GetStatePath(); + using var stream = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None); + + JsonSerializer.Serialize(stream, state, typeInfo); + return Observables.Unit; + } + catch (Exception ex) + { + return Observable.Throw(ex); } } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SaveState implementations may use serialization which requires dynamic code generation")] - [RequiresUnreferencedCode("SaveState implementations may use serialization which may require unreferenced code")] -#endif - public IObservable SaveState(object state) + /// + [RequiresUnreferencedCode("Uses reflection-based System.Text.Json serialization for 'object'. Prefer LoadState(JsonTypeInfo) for trimming/AOT.")] + [RequiresDynamicCode("Uses reflection-based System.Text.Json serialization for 'object'. Prefer LoadState(JsonTypeInfo) for trimming/AOT.")] + public IObservable LoadState() { try { - var target = Path.Combine(CreateAppDirectory(NSSearchPathDirectory.ApplicationSupportDirectory), "state.dat"); + var path = GetStatePath(); + using var stream = File.OpenRead(path); + + // Reflection-based: object deserialization typically requires metadata at runtime. + var result = JsonSerializer.Deserialize(stream); + return Observable.Return(result); + } + catch (Exception ex) + { + return Observable.Throw(ex); + } + } - using (var st = File.Open(target, FileMode.Create)) - { - JsonSerializer.Serialize(st, state); - } + /// + [RequiresUnreferencedCode("Uses reflection-based System.Text.Json serialization for generic T. Prefer SaveState(T, JsonTypeInfo) for trimming/AOT.")] + [RequiresDynamicCode("Uses reflection-based System.Text.Json serialization for generic T. Prefer SaveState(T, JsonTypeInfo) for trimming/AOT.")] + public IObservable SaveState(T state) + { + try + { + var path = GetStatePath(); + using var stream = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None); + JsonSerializer.Serialize(stream, state); return Observables.Unit; } catch (Exception ex) @@ -64,17 +146,13 @@ public IObservable SaveState(object state) } } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("InvalidateState uses JsonSerializer which requires dynamic code generation")] - [RequiresUnreferencedCode("InvalidateState uses JsonSerializer which may require unreferenced code")] -#endif + /// public IObservable InvalidateState() { try { - var target = Path.Combine(CreateAppDirectory(NSSearchPathDirectory.ApplicationSupportDirectory), "state.dat"); - File.Delete(target); + var path = GetStatePath(); + File.Delete(path); return Observables.Unit; } @@ -84,16 +162,53 @@ public IObservable InvalidateState() } } - private static string CreateAppDirectory(NSSearchPathDirectory targetDir, string subDir = "Data") + /// + /// Creates (if necessary) and returns the application storage directory beneath the specified special folder. + /// + /// The platform search path directory. + /// The application-specific subdirectory name. + /// The fully qualified directory path. + /// + /// Thrown when the platform path cannot be resolved or the bundle identifier is unavailable. + /// + private static string CreateAppDirectory(NSSearchPathDirectory targetDir, string subDir) { + // 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 _); - var ret = Path.Combine(url.RelativePath!, NSBundle.MainBundle.BundleIdentifier, subDir); - if (!Directory.Exists(ret)) + if (url is null) { - Directory.CreateDirectory(ret); + throw new InvalidOperationException("Unable to resolve platform application support directory."); } - return ret; + var bundleId = NSBundle.MainBundle?.BundleIdentifier; + if (string.IsNullOrEmpty(bundleId)) + { + throw new InvalidOperationException("Unable to resolve application bundle identifier."); + } + + var basePath = url.RelativePath; + if (string.IsNullOrEmpty(basePath)) + { + throw new InvalidOperationException("Resolved application support path was empty."); + } + + var path = Path.Combine(basePath, bundleId, subDir); + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + return path; } + + /// + /// Computes the full path to the persisted state file. + /// + /// The absolute file path. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string GetStatePath() + => Path.Combine(_appDirectory.Value, StateFileName); } diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/DateTimeNSDateConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/DateTimeNSDateConverter.cs deleted file mode 100644 index 06ea861b97..0000000000 --- a/src/ReactiveUI/Platforms/apple-common/Converters/DateTimeNSDateConverter.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2025 .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 Foundation; - -namespace ReactiveUI; - -/// -/// Binding Type Converter for DateTime to NSDateTime. -/// -public class DateTimeNSDateConverter : IBindingTypeConverter -{ - internal static Lazy Instance { get; } = new(); - - /// - public int GetAffinityForObjects(Type fromType, Type toType) => - (fromType == typeof(DateTime) && toType == typeof(NSDate)) || - (toType == typeof(DateTime) && fromType == typeof(NSDate)) ? 4 : -1; - - /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result) - { - result = null; - - if (from?.GetType() == typeof(DateTime) && toType == typeof(NSDate)) - { - var dt = (DateTime)from; - result = (NSDate)dt; - return true; - } - else if (from?.GetType() == typeof(NSDate) && toType == typeof(DateTime)) - { - var dt = (NSDate)from; - result = (DateTime)dt; - return true; - } - - return false; - } -} diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/DateTimeOffsetToNSDateConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/DateTimeOffsetToNSDateConverter.cs new file mode 100644 index 0000000000..0b7210763c --- /dev/null +++ b/src/ReactiveUI/Platforms/apple-common/Converters/DateTimeOffsetToNSDateConverter.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2025 .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 UIKIT || MACOS +using System.Diagnostics.CodeAnalysis; +using Foundation; + +namespace ReactiveUI; + +/// +/// Converts to . +/// +public sealed class DateTimeOffsetToNSDateConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 8; + + /// + public override bool TryConvert(DateTimeOffset from, object? conversionHint, [NotNullWhen(true)] out NSDate? result) + { + result = (NSDate)from.DateTime; + return true; + } +} +#endif diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/DateTimeToNSDateConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/DateTimeToNSDateConverter.cs new file mode 100644 index 0000000000..6291c56cd1 --- /dev/null +++ b/src/ReactiveUI/Platforms/apple-common/Converters/DateTimeToNSDateConverter.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2025 .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 UIKIT || MACOS +using System.Diagnostics.CodeAnalysis; +using Foundation; + +namespace ReactiveUI; + +/// +/// Converts to . +/// +public sealed class DateTimeToNSDateConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 8; + + /// + public override bool TryConvert(DateTime from, object? conversionHint, [NotNullWhen(true)] out NSDate? result) + { + result = (NSDate)from; + return true; + } +} +#endif diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeConverter.cs new file mode 100644 index 0000000000..0a37b44128 --- /dev/null +++ b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2025 .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 UIKIT || MACOS +using System.Diagnostics.CodeAnalysis; +using Foundation; + +namespace ReactiveUI; + +/// +/// Converts to . +/// +public sealed class NSDateToDateTimeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 8; + + /// + public override bool TryConvert(NSDate? from, object? conversionHint, [NotNullWhen(true)] out DateTime result) + { + if (from is null) + { + result = default; + return false; + } + + result = (DateTime)from; + return true; + } +} +#endif diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeOffsetConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeOffsetConverter.cs new file mode 100644 index 0000000000..80c0a11494 --- /dev/null +++ b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeOffsetConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2025 .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 UIKIT || MACOS +using System.Diagnostics.CodeAnalysis; +using Foundation; + +namespace ReactiveUI; + +/// +/// Converts to . +/// +public sealed class NSDateToDateTimeOffsetConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 8; + + /// + public override bool TryConvert(NSDate? from, object? conversionHint, [NotNullWhen(true)] out DateTimeOffset result) + { + if (from is null) + { + result = default; + return false; + } + + result = new DateTimeOffset((DateTime)from); + return true; + } +} +#endif diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToNullableDateTimeConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToNullableDateTimeConverter.cs new file mode 100644 index 0000000000..3ee0e99ff3 --- /dev/null +++ b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToNullableDateTimeConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2025 .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 UIKIT || MACOS +using System.Diagnostics.CodeAnalysis; +using Foundation; + +namespace ReactiveUI; + +/// +/// Converts to nullable . +/// +public sealed class NSDateToNullableDateTimeConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 8; + + /// + public override bool TryConvert(NSDate? from, object? conversionHint, [NotNullWhen(true)] out DateTime? result) + { + if (from is null) + { + result = null; + return false; + } + + result = (DateTime)from; + return true; + } +} +#endif diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToNullableDateTimeOffsetConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToNullableDateTimeOffsetConverter.cs new file mode 100644 index 0000000000..7037d15efb --- /dev/null +++ b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToNullableDateTimeOffsetConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2025 .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 UIKIT || MACOS +using System.Diagnostics.CodeAnalysis; +using Foundation; + +namespace ReactiveUI; + +/// +/// Converts to nullable . +/// +public sealed class NSDateToNullableDateTimeOffsetConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 8; + + /// + public override bool TryConvert(NSDate? from, object? conversionHint, [NotNullWhen(true)] out DateTimeOffset? result) + { + if (from is null) + { + result = null; + return false; + } + + result = new DateTimeOffset((DateTime)from); + return true; + } +} +#endif diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/NullableDateTimeOffsetToNSDateConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/NullableDateTimeOffsetToNSDateConverter.cs new file mode 100644 index 0000000000..32f0148efe --- /dev/null +++ b/src/ReactiveUI/Platforms/apple-common/Converters/NullableDateTimeOffsetToNSDateConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2025 .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 UIKIT || MACOS +using System.Diagnostics.CodeAnalysis; +using Foundation; + +namespace ReactiveUI; + +/// +/// Converts nullable to . +/// +public sealed class NullableDateTimeOffsetToNSDateConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 8; + + /// + public override bool TryConvert(DateTimeOffset? from, object? conversionHint, [NotNullWhen(true)] out NSDate? result) + { + if (!from.HasValue) + { + result = null; + return false; + } + + result = (NSDate)from.Value.DateTime; + return true; + } +} +#endif diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/NullableDateTimeToNSDateConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/NullableDateTimeToNSDateConverter.cs new file mode 100644 index 0000000000..a8e43d9e31 --- /dev/null +++ b/src/ReactiveUI/Platforms/apple-common/Converters/NullableDateTimeToNSDateConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2025 .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 UIKIT || MACOS +using System.Diagnostics.CodeAnalysis; +using Foundation; + +namespace ReactiveUI; + +/// +/// Converts nullable to . +/// +public sealed class NullableDateTimeToNSDateConverter : BindingTypeConverter +{ + /// + public override int GetAffinityForObjects() => 8; + + /// + public override bool TryConvert(DateTime? from, object? conversionHint, [NotNullWhen(true)] out NSDate? result) + { + if (!from.HasValue) + { + result = null; + return false; + } + + result = (NSDate)from.Value; + return true; + } +} +#endif diff --git a/src/ReactiveUI/Platforms/apple-common/KVOObservableForProperty.cs b/src/ReactiveUI/Platforms/apple-common/KVOObservableForProperty.cs index 33a31b09b6..88695ff103 100644 --- a/src/ReactiveUI/Platforms/apple-common/KVOObservableForProperty.cs +++ b/src/ReactiveUI/Platforms/apple-common/KVOObservableForProperty.cs @@ -9,132 +9,224 @@ using Foundation; -namespace ReactiveUI; +using ReactiveUI; /// -/// This class provides notifications for Cocoa Framework objects based on -/// Key-Value Observing. Unfortunately, this class is a bit Tricky™, because -/// of the caveat mentioned below - there is no way up-front to be able to -/// tell whether a given property on an object is Key-Value Observable, we -/// only have to hope for the best :-/. +/// Provides change notifications for Cocoa instances using Key-Value Observing (KVO). /// -[Preserve(AllMembers = true)] -[RequiresUnreferencedCode("KVOObservableForProperty uses methods that may require unreferenced code")] -public class KVOObservableForProperty : ICreatesObservableForProperty +[ReactiveUI.Preserve(AllMembers = true)] +public sealed class KVOObservableForProperty : ICreatesObservableForProperty { - private static readonly MemoizingMRUCache<(Type type, string propertyName), bool> _declaredInNSObject; + /// + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) + { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(propertyName); + + if (!typeof(NSObject).IsAssignableFrom(type)) + { + return 0; + } - static KVOObservableForProperty() + return IsDeclaredOnNSObject(type, propertyName) ? 15 : 0; + } + + /// + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + public IObservable> GetNotificationForProperty( + object sender, + Expression expression, + string propertyName, + bool beforeChanged = false, + bool suppressWarnings = false) { - var monotouchAssemblyName = typeof(NSObject).Assembly.FullName; + ArgumentNullException.ThrowIfNull(sender); + ArgumentNullException.ThrowIfNull(expression); + ArgumentNullException.ThrowIfNull(propertyName); - _declaredInNSObject = new MemoizingMRUCache<(Type type, string propertyName), bool>( - (pair, _) => - { - var thisType = pair.type; - - // Types that aren't NSObjects at all are uninteresting to us - if (!typeof(NSObject).IsAssignableFrom(thisType)) - { - return false; - } - - while (thisType is not null) - { - if (thisType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Any(x => x.Name == pair.propertyName)) - { - // NB: This is a not-completely correct way to detect if - // an object is defined in an Obj-C class (it will fail if - // you're using a binding to a 3rd-party Obj-C library). - return thisType.Assembly.FullName == monotouchAssemblyName; - } - - thisType = thisType.BaseType; - } - - // The property doesn't exist at all - return false; - }, - RxApp.BigCacheLimit); + if (sender is not NSObject) + { + throw new ArgumentException("Sender must be an NSObject.", nameof(sender)); + } + + var keyPath = GetCocoaKeyPathUnsafe(sender.GetType(), propertyName); + + return GetNotificationForProperty( + sender, + expression, + propertyName, + keyPath, + beforeChanged, + suppressWarnings); } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif - public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) => - _declaredInNSObject.Get((type, propertyName)) ? 15 : 0; - - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] -#endif - public IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) + /// + /// Subscribes to KVO change notifications using a pre-resolved observation key (KVO key path). + /// + /// + /// + /// This helper wires NSObject AddObserver/RemoveObserver patterns + /// to an sequence and ensures deterministic unsubscription. + /// + /// + /// The returned disposable is idempotent and will remove the observer and release the pinned delegate instance. + /// + /// + /// The object to observe. Must be an . + /// + /// The expression describing the observed member. This value is surfaced in emitted + /// instances. + /// + /// The .NET property name being observed. + /// The Cocoa KVO key path to observe. + /// + /// If , request notifications using ; otherwise + /// request notifications using . + /// + /// If , warnings should not be logged. + /// + /// An observable that produces an whenever the KVO key path changes. + /// + /// + /// Thrown when , , , or + /// is . + /// + /// Thrown when is not an . + private static IObservable> GetNotificationForProperty( + object sender, + Expression expression, + string propertyName, + string observationKey, + bool beforeChanged = false, + bool suppressWarnings = false) { + ArgumentNullException.ThrowIfNull(sender); + ArgumentNullException.ThrowIfNull(expression); + ArgumentNullException.ThrowIfNull(propertyName); + ArgumentNullException.ThrowIfNull(observationKey); + if (sender is not NSObject obj) { - throw new ArgumentException("Sender isn't an NSObject"); + throw new ArgumentException("Sender must be an NSObject.", nameof(sender)); } - return Observable.Create>(subj => + return Observable.Create>(observer => { - var bobs = new BlockObserveValueDelegate((__, s, _) => - subj.OnNext(new ObservedChange(s, expression, default))); + ArgumentNullException.ThrowIfNull(observer); + + // Create a single stable delegate instance; KVO removal requires the same observer instance. + var callback = new BlockObserveValueDelegate((unusedKeyPath, observedObject, unusedChange) => + observer.OnNext(new ObservedChange(observedObject, expression, default))); - var pin = GCHandle.Alloc(bobs); + // Ensure the delegate is kept alive for the lifetime of the subscription. + var handle = GCHandle.Alloc(callback); - var keyPath = (NSString)FindCocoaNameFromNetName(sender.GetType(), propertyName); + var keyPath = (NSString)observationKey; - obj.AddObserver(bobs, keyPath, beforeChanged ? NSKeyValueObservingOptions.Old : NSKeyValueObservingOptions.New, IntPtr.Zero); + obj.AddObserver( + callback, + keyPath, + beforeChanged ? NSKeyValueObservingOptions.Old : NSKeyValueObservingOptions.New, + IntPtr.Zero); return Disposable.Create(() => { - obj.RemoveObserver(bobs, keyPath); - pin.Free(); + obj.RemoveObserver(callback, keyPath); + handle.Free(); }); }); } - private static string FindCocoaNameFromNetName([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type senderType, string propertyName) + /// + /// Determines whether the specified member name is declared on the type hierarchy rooted at . + /// + /// The runtime type to inspect. + /// The member name to test. + /// + /// if the member name is present on the inspected hierarchy; otherwise . + /// + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + private static bool IsDeclaredOnNSObject( + Type type, + string propertyName) { - var propIsBoolean = false; + var monotouchAssemblyName = typeof(NSObject).Assembly.FullName; - var pi = senderType.GetTypeInfo().DeclaredProperties.FirstOrDefault(static x => !x.IsStatic()); - if (pi is null) + var current = type; + while (current is not null) { - goto attemptGuess; - } + // Search only public instance members declared at this level. + var members = current.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); - if (pi.DeclaringType == typeof(bool)) - { - propIsBoolean = true; + for (var i = 0; i < members.Length; i++) + { + if (string.Equals(members[i].Name, propertyName, StringComparison.Ordinal)) + { + // Historical heuristic: treat it as Obj-C-backed if it originates from the NSObject assembly. + return string.Equals(current.Assembly.FullName, monotouchAssemblyName, StringComparison.Ordinal); + } + } + + current = current.BaseType; } - var mi = pi.GetGetMethod(); - if (mi is null) - { - goto attemptGuess; - } + // The member doesn't exist on the hierarchy. + return false; + } - var attr = mi.GetCustomAttributes(true).OfType().FirstOrDefault(); - if (attr is null) - { - goto attemptGuess; - } + /// + /// Maps a .NET property name to an Objective-C selector / KVO key path using reflection over the runtime type. + /// + /// + /// + /// This method inspects the runtime type for an exported selector attribute on the getter, and falls back to a + /// naming convention when no export is found. + /// + /// + /// Trimming/AOT: this method reflects over runtime types and is not trimming-safe. + /// + /// + /// The runtime type of the sender. + /// The .NET property name. + /// The derived Cocoa key path to use for KVO. + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + 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()); - if (attr.Selector is not null) + var propIsBoolean = false; + + if (property is not null) { - return attr.Selector; + propIsBoolean = property.PropertyType == typeof(bool); + + var getter = property.GetGetMethod(); + if (getter is not null) + { + var export = getter + .GetCustomAttributes(inherit: true) + .OfType() + .FirstOrDefault(); + + if (export?.Selector is not null) + { + return export.Selector; + } + } } - attemptGuess: if (propIsBoolean) { propertyName = "Is" + propertyName; } - return string.Concat(char.ToLowerInvariant(propertyName[0]).ToString(CultureInfo.InvariantCulture), propertyName.AsSpan(1)); + return string.Concat( + char.ToLowerInvariant(propertyName[0]).ToString(CultureInfo.InvariantCulture), + propertyName.AsSpan(1)); } } diff --git a/src/ReactiveUI/Platforms/apple-common/ObservableForPropertyBase.cs b/src/ReactiveUI/Platforms/apple-common/ObservableForPropertyBase.cs index b09031c4a0..8a6319ab7c 100644 --- a/src/ReactiveUI/Platforms/apple-common/ObservableForPropertyBase.cs +++ b/src/ReactiveUI/Platforms/apple-common/ObservableForPropertyBase.cs @@ -3,6 +3,13 @@ // 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; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Reactive.Disposables; +using System.Reactive.Linq; + using Foundation; #if UIKIT @@ -14,150 +21,360 @@ namespace ReactiveUI; /// -/// ObservableForPropertyBase represents an object that knows how to -/// create notifications for a given type of object. Implement this if you -/// are porting RxUI to a new UI toolkit, or generally want to enable WhenAny -/// for another type of object that can be observed in a unique way. +/// Represents an object that knows how to create notifications for a given type of object. +/// Implement this when porting ReactiveUI to a new UI toolkit, or to enable WhenAny* +/// support for another type that can be observed in a unique way. /// +/// +/// Implementations typically call +/// during construction to populate supported properties. +/// [Preserve] public abstract class ObservableForPropertyBase : ICreatesObservableForProperty { /// - /// Configuration map. + /// Message used for annotations on reflection-based event hookup. + /// + private const string RequiresUnreferencedCodeMessage = + "String-based event hookup uses reflection over members that may be trimmed."; + + /// + /// Message used for annotations on reflection-based event hookup. + /// + private const string RequiresDynamicCodeMessage = + "String-based event hookup may require runtime code generation and is not guaranteed to be AOT-compatible."; + + /// + /// Synchronization gate protecting and . + /// + private readonly object _gate = new(); + + /// + /// Configuration map keyed by registered type and then by property name. /// private readonly Dictionary> _config = []; + /// + /// Cache of the best matching registration for a runtime type and property name. + /// + /// + /// Entries are versioned so that updates via invalidate previous results without + /// requiring global cache clearing under lock. + /// + private readonly ConcurrentDictionary<(RuntimeTypeHandle Type, string Property), CacheEntry> _bestMatchCache = new(); + + /// + /// Monotonically increasing version for used to invalidate . + /// + private int _version; + /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) { - if (beforeChanged) - { - return 0; - } - - var match = _config.Keys - .Where(x => x.IsAssignableFrom(type) && _config[x].ContainsKey(propertyName)) - .Select(x => _config[x][propertyName]) - .OrderByDescending(x => x.Affinity) - .FirstOrDefault(); + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(propertyName); - if (match is null) + if (beforeChanged) { return 0; } - return match.Affinity; + var match = ResolveBestMatch(type, propertyName); + return match is null ? 0 : match.Affinity; } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public IObservable> GetNotificationForProperty( - object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) + object sender, + Expression expression, + string propertyName, + bool beforeChanged = false, + bool suppressWarnings = false) { ArgumentExceptionHelper.ThrowIfNull(sender); + ArgumentExceptionHelper.ThrowIfNull(expression); + ArgumentExceptionHelper.ThrowIfNull(propertyName); if (beforeChanged) { - return Observable>.Never; + return Observable>.Never; } var type = sender.GetType(); + var match = ResolveBestMatch(type, propertyName); - var match = _config.Keys - .Where(x => x.IsAssignableFrom(type) && _config[x].ContainsKey(propertyName)) - .Select(x => _config[x][propertyName]) - .OrderByDescending(x => x.Affinity) - .FirstOrDefault(); + if (match is null) + { + throw new NotSupportedException($"Notifications for {type.Name}.{propertyName} are not supported"); + } - return match is null - ? throw new NotSupportedException($"Notifications for {type.Name}.{propertyName} are not supported") - : match.CreateObservable.Invoke((NSObject)sender, expression); + // Do not invoke user-provided observable factories under lock. + return match.CreateObservable.Invoke((NSObject)sender, expression); } #if UIKIT /// - /// Creates an Observable for a UIControl Event. + /// Creates an observable sequence that produces a notification each time the given + /// is raised by the . /// - /// An observable. - /// The sender. - /// The expression. + /// The native sender. + /// The expression associated with the observed change. /// The control event to listen for. - protected static IObservable> ObservableFromUIControlEvent(NSObject sender, Expression expression, UIControlEvent evt) => - Observable.Create>(subj => + /// An observable sequence of observed changes. + protected static IObservable> ObservableFromUIControlEvent( + NSObject sender, + Expression expression, + UIControlEvent evt) => + Observable.Create>(observer => { var control = (UIControl)sender; - void Handler(object? s, EventArgs e) => subj.OnNext(new ObservedChange(sender, expression, default)); + // Stable delegate allows deterministic unsubscription. + void Handler(object? s, EventArgs e) => + observer.OnNext(new ObservedChange(sender, expression, default)); control.AddTarget(Handler, evt); - return Disposable.Create(() => control.RemoveTarget(Handler, evt)); }); #endif /// - /// Creates an Observable for a NSNotificationCenter notification. + /// Creates an observable sequence that produces a notification each time the specified + /// notification is posted for . /// - /// The from notification. - /// Sender. - /// Expression. - /// Notification. - protected static IObservable> ObservableFromNotification(NSObject sender, Expression expression, NSString notification) => - Observable.Create>(subj => + /// The native sender. + /// The expression associated with the observed change. + /// The notification name. + /// An observable sequence of observed changes. + protected static IObservable> ObservableFromNotification( + NSObject sender, + Expression expression, + NSString notification) => + Observable.Create>(observer => { var handle = NSNotificationCenter.DefaultCenter.AddObserver( notification, - _ => subj.OnNext(new ObservedChange(sender, expression, default)), + _ => observer.OnNext(new ObservedChange(sender, expression, default)), sender); return Disposable.Create(() => NSNotificationCenter.DefaultCenter.RemoveObserver(handle)); }); /// - /// Creates an Observable for a NSNotificationCenter notification. + /// Creates an observable sequence from an event using reflection-based string event lookup. /// - /// The from notification. - /// Sender. - /// The expression. + /// + /// Prefer the add/remove overloads (for example, + /// ) + /// for trimming/AOT compatibility. + /// + /// The native sender. + /// The expression associated with the observed change. /// The event name. - [RequiresUnreferencedCode("ObservableFromEvent uses methods that may require unreferenced code")] - protected static IObservable> ObservableFromEvent(NSObject sender, Expression expression, string eventName) => - Observable.Create>(subj => - Observable.FromEventPattern(sender, eventName).Subscribe(_ => - subj.OnNext(new ObservedChange(sender, expression, default)))); + /// An observable sequence of observed changes. + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] + protected static IObservable> ObservableFromEvent( + NSObject sender, + Expression expression, + string eventName) => + Observable.Create>(observer => + Observable + .FromEventPattern(sender, eventName) + .Subscribe(_ => observer.OnNext(new ObservedChange(sender, expression, default)))); + + /// + /// Creates an observable sequence from an event using explicit add/remove handlers (non-reflection). + /// + /// The sender type. + /// The sender instance. + /// The expression associated with the observed change. + /// Adds the handler to the event source. + /// Removes the handler from the event source. + /// An observable sequence of observed changes. + protected static IObservable> ObservableFromEvent( + TSender sender, + Expression expression, + Action addHandler, + Action removeHandler) + where TSender : NSObject => + Observable.Create>(observer => + { + // Stable handler for deterministic unsubscription. + void Handler(object? s, EventArgs e) => + observer.OnNext(new ObservedChange(sender, expression, default)); + + addHandler(Handler); + return Disposable.Create(() => removeHandler(Handler)); + }); + + /// + /// Creates an observable sequence from a typed event using explicit add/remove handlers (non-reflection). + /// + /// The sender type. + /// The event args type. + /// The sender instance. + /// The expression associated with the observed change. + /// Adds the handler to the event source. + /// Removes the handler from the event source. + /// An observable sequence of observed changes. + protected static IObservable> ObservableFromEvent( + TSender sender, + Expression expression, + Action> addHandler, + Action> removeHandler) + where TSender : NSObject + where TEventArgs : EventArgs => + Observable.Create>(observer => + { + // Stable handler for deterministic unsubscription. + void Handler(object? s, TEventArgs e) => + observer.OnNext(new ObservedChange(sender, expression, default)); + + addHandler(Handler); + return Disposable.Create(() => removeHandler(Handler)); + }); + + /// + /// Registers an observable factory for the specified and . + /// + /// The type the property belongs to. + /// The property name. + /// The affinity score for this registration. + /// Factory that creates the observable for this property. + /// + /// Thrown when , , or is . + /// + protected void Register( + Type type, + string property, + int affinity, + Func>> createObservable) + { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(property); + ArgumentNullException.ThrowIfNull(createObservable); + + lock (_gate) + { + if (!_config.TryGetValue(type, out var typeProperties)) + { + typeProperties = []; + _config[type] = typeProperties; + } + + typeProperties[property] = new ObservablePropertyInfo(affinity, createObservable); + + // Invalidate caches by bumping version. + unchecked + { + _version++; + } + } + } /// - /// Registers an observable factory for the specified type and property. + /// Resolves the best registered match for a runtime type and property name, using a versioned cache. /// - /// Type. - /// Property. - /// Affinity. - /// Create observable. - protected void Register(Type type, string property, int affinity, Func>> createObservable) + /// The runtime type to resolve. + /// The property name to resolve. + /// The best matching registration, or if none exists. + private ObservablePropertyInfo? ResolveBestMatch(Type runtimeType, string propertyName) { - if (!_config.TryGetValue(type, out var typeProperties)) + // Fast path: check cache. + var key = (runtimeType.TypeHandle, propertyName); + if (_bestMatchCache.TryGetValue(key, out var cached)) { - typeProperties = []; - _config[type] = typeProperties; + // If config has not changed since the cached entry was computed, return it. + if (cached.Version == _version) + { + return cached.Info; + } } - typeProperties[property] = new ObservablePropertyInfo(affinity, createObservable); + // Slow path: compute under lock against a consistent snapshot of config. + ObservablePropertyInfo? best = null; + var versionSnapshot = 0; + + lock (_gate) + { + versionSnapshot = _version; + + foreach (var kvp in _config) + { + var registeredType = kvp.Key; + + if (!registeredType.IsAssignableFrom(runtimeType)) + { + continue; + } + + if (!kvp.Value.TryGetValue(propertyName, out var info)) + { + continue; + } + + if (best is null || info.Affinity > best.Affinity) + { + best = info; + } + } + } + + // Publish computed value to cache (including null, to avoid repeated scans for unsupported properties). + _bestMatchCache[key] = new CacheEntry(versionSnapshot, best); + return best; } - internal record ObservablePropertyInfo + /// + /// Represents a cached best-match result for a (runtime type, property) pair. + /// + private readonly record struct CacheEntry + { + /// + /// Initializes a new instance of the struct. + /// Initializes a new instance of the record. + /// + /// The configuration version the entry was computed from. + /// The resolved property information, or if unsupported. + public CacheEntry(int version, ObservablePropertyInfo? info) => (Version, Info) = (version, info); + + /// + /// Gets the configuration version the entry was computed from. + /// + public int Version { get; } + + /// + /// Gets the resolved property information, or if unsupported. + /// + public ObservablePropertyInfo? Info { get; } + } + + /// + /// Describes an observable factory registration for a property, including its affinity. + /// + internal sealed record ObservablePropertyInfo { - public ObservablePropertyInfo(int affinity, Func>> createObservable) => + /// + /// Initializes a new instance of the class. + /// + /// The affinity score for the registration. + /// The factory for creating the observable for this property. + public ObservablePropertyInfo( + int affinity, + Func>> createObservable) => (Affinity, CreateObservable) = (affinity, createObservable); + /// + /// Gets the affinity score for the registration. + /// public int Affinity { get; } + /// + /// Gets the observable factory for the registration. + /// public Func>> CreateObservable { get; } } } diff --git a/src/ReactiveUI/Platforms/apple-common/ReactiveControl.cs b/src/ReactiveUI/Platforms/apple-common/ReactiveControl.cs index 3f053c6c61..9a44b28620 100644 --- a/src/ReactiveUI/Platforms/apple-common/ReactiveControl.cs +++ b/src/ReactiveUI/Platforms/apple-common/ReactiveControl.cs @@ -21,10 +21,6 @@ namespace ReactiveUI; /// This is a UIControl that is both and UIControl and has a ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveControl inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveControl inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveControl : UIControl, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate, ICanForceManualActivation { private readonly Subject _deactivated = new(); @@ -160,10 +156,6 @@ protected override void Dispose(bool disposing) /// /// The view model type. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveControl inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveControl inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveControl : ReactiveControl, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/apple-common/ReactiveImageView.cs b/src/ReactiveUI/Platforms/apple-common/ReactiveImageView.cs index 5c8d7cc482..650dc121fd 100644 --- a/src/ReactiveUI/Platforms/apple-common/ReactiveImageView.cs +++ b/src/ReactiveUI/Platforms/apple-common/ReactiveImageView.cs @@ -21,10 +21,6 @@ namespace ReactiveUI; /// This is an ImageView that is both and ImageView and has a ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveImageView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveImageView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveImageView : NSImageView, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate, ICanForceManualActivation { private readonly Subject _activated = new(); @@ -169,10 +165,6 @@ protected override void Dispose(bool disposing) /// /// The view model type. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveImageView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveImageView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveImageView : ReactiveImageView, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/apple-common/ReactiveSplitViewController.cs b/src/ReactiveUI/Platforms/apple-common/ReactiveSplitViewController.cs index f1ed531ce0..2e514b3a51 100644 --- a/src/ReactiveUI/Platforms/apple-common/ReactiveSplitViewController.cs +++ b/src/ReactiveUI/Platforms/apple-common/ReactiveSplitViewController.cs @@ -17,10 +17,6 @@ namespace ReactiveUI; /// This is a View that is both a NSSplitViewController and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveSplitViewController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveSplitViewController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveSplitViewController : NSSplitViewController, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { private readonly Subject _activated = new(); @@ -162,10 +158,6 @@ protected override void Dispose(bool disposing) /// /// The view model type. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveSplitViewController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveSplitViewController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveSplitViewController : ReactiveSplitViewController, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/apple-common/ReactiveView.cs b/src/ReactiveUI/Platforms/apple-common/ReactiveView.cs index b2201a7552..74f482d503 100644 --- a/src/ReactiveUI/Platforms/apple-common/ReactiveView.cs +++ b/src/ReactiveUI/Platforms/apple-common/ReactiveView.cs @@ -19,10 +19,6 @@ namespace ReactiveUI; /// This is a View that is both a NSView and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveView : NSView, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate, ICanForceManualActivation { private readonly Subject _activated = new(); @@ -151,10 +147,6 @@ protected override void Dispose(bool disposing) /// /// The view model type. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveView : ReactiveView, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/apple-common/ReactiveViewController.cs b/src/ReactiveUI/Platforms/apple-common/ReactiveViewController.cs index d37fb227fb..d4ef0cf6be 100644 --- a/src/ReactiveUI/Platforms/apple-common/ReactiveViewController.cs +++ b/src/ReactiveUI/Platforms/apple-common/ReactiveViewController.cs @@ -17,10 +17,6 @@ namespace ReactiveUI; /// This is a View that is both a NSViewController and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveViewController uses ReactiveUI extension methods which require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveViewController uses ReactiveUI extension methods which may require unreferenced code")] -#endif public class ReactiveViewController : NSViewController, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { private readonly Subject _activated = new(); @@ -159,10 +155,6 @@ protected override void Dispose(bool disposing) /// /// The view model type. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveViewController uses ReactiveUI extension methods which require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveViewController uses ReactiveUI extension methods which may require unreferenced code")] -#endif public abstract class ReactiveViewController : ReactiveViewController, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/apple-common/TargetActionCommandBinder.cs b/src/ReactiveUI/Platforms/apple-common/TargetActionCommandBinder.cs index 04497dcea3..7b6dee8935 100644 --- a/src/ReactiveUI/Platforms/apple-common/TargetActionCommandBinder.cs +++ b/src/ReactiveUI/Platforms/apple-common/TargetActionCommandBinder.cs @@ -3,7 +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. +#nullable enable + +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Runtime.CompilerServices; using System.Windows.Input; using Foundation; @@ -19,140 +24,338 @@ namespace ReactiveUI; /// -/// TargetActionCommandBinder is an implementation of command binding that -/// understands Cocoa's Target / Action Framework. Many controls in Cocoa -/// that are effectively command sources (i.e. Buttons, Menus, etc), -/// participate in this framework. +/// An implementation that binds commands using Cocoa's +/// Target/Action mechanism. /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("TargetActionCommandBinder uses reflection for property access and Objective-C runtime features which require dynamic code generation")] -[RequiresUnreferencedCode("TargetActionCommandBinder uses reflection for property access and Objective-C runtime features which may require unreferenced code")] -#endif +/// +/// +/// Many Cocoa controls (buttons, menu items, toolbar items, etc.) participate in the Target/Action pattern. +/// This binder sets the control's Target and (when present) Action properties to route UI +/// invocations to an . +/// +/// +/// Trimming/AOT: the Target/Action path reflects over an unknown runtime type to locate properties named +/// Target, Action, and optionally Enabled. This is not trimming-safe and is annotated accordingly. +/// Prefer the add/remove handler overloads on where applicable. +/// +/// public class TargetActionCommandBinder : ICreatesCommandBinding { - private readonly Type[] _validTypes; - +#if UIKIT /// - /// Initializes a new instance of the class. + /// The set of Cocoa types that are valid Target/Action hosts in UIKit builds. /// - public TargetActionCommandBinder() => -#if UIKIT - _validTypes = - [ - typeof(UIControl), - ]; + private static readonly Type[] ValidTypes = [typeof(UIControl)]; #else - _validTypes = - [ - typeof(NSControl), - typeof(NSCell), - typeof(NSMenu), - typeof(NSMenuItem), - typeof(NSToolbarItem), - ]; + /// + /// The set of Cocoa types that are valid Target/Action hosts in AppKit builds. + /// + private static readonly Type[] ValidTypes = + [ + typeof(NSControl), + typeof(NSCell), + typeof(NSMenu), + typeof(NSMenuItem), + typeof(NSToolbarItem), + ]; #endif + /// + /// Cache of runtime property setters used to apply Target/Action/Enabled on Cocoa objects. + /// + /// + /// + /// This is stable type metadata and is therefore cached indefinitely. Eviction provides no value here and + /// would re-trigger reflection and setter generation. + /// + /// + /// A Action setter indicates the type does not expose an Action property. + /// An Enabled setter is optional. + /// + /// + private static readonly ConcurrentDictionary PropertySetterCache = new(); + /// - public int GetAffinityForObject(Type type, bool hasEventTarget) + public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>(bool hasEventTarget) { - if (!_validTypes.Any(x => x.IsAssignableFrom(type))) + if (hasEventTarget) { return 0; } - return !hasEventTarget ? 4 : 0; - } - - /// -#if NET6_0_OR_GREATER - public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>( - bool hasEventTarget) -#else - public int GetAffinityForObject( - bool hasEventTarget) -#endif - { - if (!_validTypes.Any(static x => x.IsAssignableFrom(typeof(T)))) + var t = typeof(T); + for (var i = 0; i < ValidTypes.Length; i++) { - return 0; + if (ValidTypes[i].IsAssignableFrom(t)) + { + return 4; + } } - return !hasEventTarget ? 4 : 0; + return 0; } /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindCommandToObject uses Reflection.GetValueSetterOrThrow and GetValueSetterForProperty which require dynamic code generation")] - [RequiresUnreferencedCode("BindCommandToObject uses Reflection.GetValueSetterOrThrow and GetValueSetterForProperty which may require unreferenced code")] -#endif - public IDisposable? BindCommandToObject(ICommand? command, object? target, IObservable commandParameter) + /// + /// + /// This binds via Target/Action, not via a .NET event. It requires that the runtime type exposes a Target + /// property and that a selector named theAction: can be invoked on the target. + /// + /// + /// If the runtime type also exposes an Enabled property, it is synchronized with + /// and . + /// + /// + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T>( + ICommand? command, + T? target, + IObservable commandParameter) + where T : class { - ArgumentExceptionHelper.ThrowIfNull(command); ArgumentExceptionHelper.ThrowIfNull(target); - commandParameter ??= Observable.Return(target); + // Match other binders: null command means "no binding". + if (command is null) + { + return Disposable.Empty; + } + + commandParameter ??= Observable.Return((object?)target); object? latestParam = null; - var ctlDelegate = new ControlDelegate( - _ => + + // Cocoa routes UI actions to a selector on the target; we provide a stable NSObject instance. + var ctlDelegate = new ControlDelegate(static _ => { }) + { + // IsEnabled is used on AppKit to validate menu items; keep it aligned with CanExecute. + IsEnabled = command.CanExecute(null), + }; + + // Avoid capturing in the Export method; store the block on the delegate. + ctlDelegate.SetBlock(_ => + { + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) { - if (command!.CanExecute(latestParam)) - { - command.Execute(latestParam); - } - }) - { IsEnabled = command!.CanExecute(latestParam) }; + command.Execute(param); + } + }); - var sel = new Selector("theAction:"); + // Selector name must match [Export] on ControlDelegate. + var selector = new Selector("theAction:"); - // TODO how does this work? Is there an Action property? - Reflection.GetValueSetterOrThrow(target!.GetType().GetRuntimeProperty("Action"))?.Invoke(target, sel, null); + var runtimeType = target.GetType(); + var setters = PropertySetterCache.GetOrAdd(runtimeType, static t => BuildSetters(t)); - var targetSetter = Reflection.GetValueSetterOrThrow(target.GetType().GetRuntimeProperty("Target")); - targetSetter?.Invoke(target, ctlDelegate, null); - var actionDisp = Disposable.Create(() => targetSetter?.Invoke(target, null, null)); + // Apply Action (if present) and Target (required). + setters.ActionSetter?.Invoke(target, selector, null); + setters.TargetSetter.Invoke(target, ctlDelegate, null); - var enabledSetter = Reflection.GetValueSetterForProperty(target.GetType().GetRuntimeProperty("Enabled")); - if (enabledSetter is null) + // Ensure we always detach target (and action if applicable) on dispose. + var detach = Disposable.Create(() => { - return actionDisp; + // Clear Target first to stop invocation, then clear Action (if available). + setters.TargetSetter.Invoke(target, null, null); + setters.ActionSetter?.Invoke(target, null, null); + }); + + // If Enabled isn't supported, binding is complete. + if (setters.EnabledSetter is null) + { + // Still track parameters so command execution uses the latest, but do not attempt Enabled sync. + return new CompositeDisposable( + detach, + commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x))); } - // initial enabled state - enabledSetter(target, command.CanExecute(latestParam), null); + // Initial enabled state. + setters.EnabledSetter.Invoke(target, command.CanExecute(Volatile.Read(ref latestParam)), null); + ctlDelegate.IsEnabled = command.CanExecute(Volatile.Read(ref latestParam)); - return new CompositeDisposable( - actionDisp, - commandParameter.Subscribe(x => latestParam = x), - Observable.FromEvent( + // Keep Enabled (and AppKit validate) in sync with CanExecuteChanged. + var canExecuteChangedSub = Observable.FromEvent( eventHandler => { - void Handler(object? sender, EventArgs e) => eventHandler(command.CanExecute(latestParam)); + void Handler(object? s, EventArgs e) => + eventHandler(command.CanExecute(Volatile.Read(ref latestParam))); return Handler; }, - x => command.CanExecuteChanged += x, - x => command.CanExecuteChanged -= x) - .Subscribe(x => - { - enabledSetter(target, x, null); - ctlDelegate.IsEnabled = x; - })); + h => command.CanExecuteChanged += h, + h => command.CanExecuteChanged -= h) + .Subscribe(x => + { + setters.EnabledSetter.Invoke(target, x, null); + ctlDelegate.IsEnabled = x; + }); + + return new CompositeDisposable( + detach, + commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)), + canExecuteChangedSub); } /// - public IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter, string eventName) - where TEventArgs : EventArgs => throw new NotImplementedException(); + /// + /// This overload binds to a named .NET event. It is reflection-based and therefore not trimming-safe. + /// Prefer the add/remove handler overload when you can supply delegates. + /// + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject( + ICommand? command, + T? target, + IObservable commandParameter, + string eventName) + where T : class + { + ArgumentExceptionHelper.ThrowIfNull(target); + + if (command is null) + { + return Disposable.Empty; + } + + ArgumentExceptionHelper.ThrowIfNull(eventName); - private class ControlDelegate(Action block) : NSObject + commandParameter ??= Observable.Return((object?)target); + + object? latestParam = null; + + // Stable handler for deterministic unsubscription is provided by Rx's FromEventPattern. + var evt = Observable.FromEventPattern(target, eventName); + + var paramSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); + var evtSub = evt.Subscribe(_ => + { + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) + { + command.Execute(param); + } + }); + + return new CompositeDisposable(paramSub, evtSub); + } + + /// + /// + /// This overload is fully AOT-compatible and should be preferred when an explicit event subscription API is available. + /// + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>( + ICommand? command, + T? target, + IObservable commandParameter, + Action> addHandler, + Action> removeHandler) + where T : class + where TEventArgs : EventArgs { - private readonly Action _block = block; + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(addHandler); + ArgumentNullException.ThrowIfNull(removeHandler); + if (command is null) + { + return Disposable.Empty; + } + + commandParameter ??= Observable.Return((object?)target); + + object? latestParam = null; + + void Handler(object? sender, TEventArgs e) + { + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } + + var paramSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); + addHandler(Handler); + + return new CompositeDisposable( + paramSub, + Disposable.Create(() => removeHandler(Handler))); + } + + /// + /// Creates and caches property setters required for Target/Action binding on the specified runtime type. + /// + /// The runtime type to inspect. + /// The cached setter bundle. + /// Thrown when the runtime type does not expose a required Target property. + [RequiresUnreferencedCode("Cocoa Target/Action binding reflects over runtime types to locate properties that may be removed by trimming.")] + private static Setters BuildSetters(Type type) + { + var actionProp = type.GetRuntimeProperty("Action"); + var targetProp = type.GetRuntimeProperty("Target"); + var enabledProp = type.GetRuntimeProperty("Enabled"); + + if (targetProp is null) + { + throw new InvalidOperationException( + $"Target property is required for {nameof(TargetActionCommandBinder)} on type {type.FullName}."); + } + + return new Setters( + ActionSetter: actionProp is not null ? Reflection.GetValueSetterOrThrow(actionProp) : null, + TargetSetter: Reflection.GetValueSetterOrThrow(targetProp), + EnabledSetter: enabledProp is not null ? Reflection.GetValueSetterForProperty(enabledProp) : null); + } + + /// + /// Represents the set of cached setters required to wire Target/Action and optionally Enabled. + /// + private readonly record struct Setters( + Action? ActionSetter, + Action TargetSetter, + Action? EnabledSetter); + + /// + /// Delegate object installed as the Cocoa Target for the theAction: selector. + /// + /// + /// This object must remain alive for the binding lifetime; it is referenced by the bound control's Target. + /// + private sealed class ControlDelegate : NSObject + { + private Action _block; + + /// + /// Initializes a new instance of the class. + /// + /// The action invoked when the control fires the bound selector. + public ControlDelegate(Action block) => _block = block; + + /// + /// Gets or sets a value indicating whether the command is currently executable. + /// + /// + /// On AppKit, this is used by validateMenuItem: to control menu item enabled state. + /// public bool IsEnabled { get; set; } + /// + /// Replaces the action invoked by . + /// + /// The new block to invoke. + public void SetBlock(Action block) => _block = block; + + /// + /// Selector invoked by Cocoa controls for Target/Action. + /// + /// The sender object. [Export("theAction:")] public void TheAction(NSObject sender) => _block(sender); #if !UIKIT + /// + /// AppKit menu item validation hook used to enable/disable menu items. + /// + /// The menu item being validated. + /// if the item should be enabled; otherwise . [Export("validateMenuItem:")] public bool ValidateMenuItem(NSMenuItem menuItem) => IsEnabled; #endif diff --git a/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs b/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs index ccb9c7943a..4432c70f4f 100644 --- a/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs +++ b/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs @@ -42,17 +42,51 @@ namespace ReactiveUI; /// ]]> /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp properties which require dynamic code generation")] -[RequiresUnreferencedCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp properties which may require unreferenced code")] -#endif +[RequiresUnreferencedCode("This class 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 class ViewModelViewHost : ReactiveViewController { + /// + /// Tracks the currently-adopted view controller and ensures it is disowned on replacement or disposal. + /// private readonly SerialDisposable _currentView; - private readonly ObservableAsPropertyHelper _viewContract; + + /// + /// Holds subscriptions created during initialization. + /// + private readonly CompositeDisposable _subscriptions; + + /// + /// Holds the subscription to (the inner observable) and swaps it when the + /// property changes. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed by _subscriptions")] + private readonly SerialDisposable _viewContractObservableSubscription; + + /// + /// Backing field for . This is updated by observing + /// and is raised as a property change for bindings. + /// + private string? _viewContract; + + /// + /// Backing field for . + /// private IViewLocator? _viewLocator; + + /// + /// Backing field for . + /// private NSViewController? _defaultContent; + + /// + /// Backing field for . + /// private object? _viewModel; + + /// + /// Backing field for . + /// private IObservable? _viewContractObservable; /// @@ -61,9 +95,17 @@ public class ViewModelViewHost : ReactiveViewController public ViewModelViewHost() { _currentView = new SerialDisposable(); - _viewContract = this - .WhenAnyObservable(static x => x.ViewContractObservable) - .ToProperty(this, static x => x.ViewContract, initialValue: null, scheduler: RxSchedulers.MainThreadScheduler); + _subscriptions = new CompositeDisposable(); + _viewContractObservableSubscription = new SerialDisposable(); + + // 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. + var contractStream = CreateViewContractStream() + .ObserveOn(RxSchedulers.MainThreadScheduler) + .Subscribe(SetViewContract); + + _subscriptions.Add(contractStream); + _subscriptions.Add(_viewContractObservableSubscription); Initialize(); } @@ -112,7 +154,7 @@ public IObservable? ViewContractObservable /// public string? ViewContract { - get => _viewContract.Value; + get => _viewContract; set => ViewContractObservable = Observable.Return(value); } @@ -123,11 +165,18 @@ protected override void Dispose(bool disposing) if (disposing) { + _subscriptions.Dispose(); _currentView.Dispose(); - _viewContract.Dispose(); } } + /// + /// Adds as a child controller of and ensures its view fills + /// the parent bounds. + /// + /// The parent controller. + /// The child controller to adopt. + /// Thrown when the parent's view is . private static void Adopt(NSViewController parent, NSViewController? child) { ArgumentExceptionHelper.ThrowIfNull(parent); @@ -174,6 +223,11 @@ private static void Adopt(NSViewController parent, NSViewController? child) #endif } + /// + /// Removes from its parent controller and removes its view from the view hierarchy. + /// + /// The child controller to disown. + /// Thrown when the child's view is . private static void Disown(NSViewController child) { if (child.View is null) @@ -188,57 +242,137 @@ private static void Disown(NSViewController child) child.RemoveFromParentViewController(); } + /// + /// Initializes reactive subscriptions that drive view resolution and controller swapping. + /// + [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.")] private void Initialize() { - var viewChange = this.WhenAnyValue(nameof(ViewModel)) - .CombineLatest( - this.WhenAnyObservable(x => x.ViewContractObservable).StartWith((string?)null), - (vm, contract) => new { ViewModel = vm, Contract = contract }) - .Where(x => x.ViewModel is not null); - - var defaultViewChange = this.WhenAnyValue(nameof(ViewModel)) - .CombineLatest( - this.WhenAnyValue(nameof(DefaultContent)), - (vm, defaultContent) => new { ViewModel = vm, DefaultContent = defaultContent }) - .Where(x => x.ViewModel is null && x.DefaultContent is not null) - .Select(x => x.DefaultContent); - - viewChange - .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe( - x => - { - var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; - var view = viewLocator.ResolveView(x.ViewModel, x.Contract); - - if (view is null) + var viewModelChanges = ObserveProperty(static x => x.ViewModel, nameof(ViewModel)); + var defaultContentChanges = ObserveProperty(static x => x.DefaultContent, nameof(DefaultContent)); + var contractChanges = ObserveProperty(static x => x.ViewContract, nameof(ViewContract)); + + var viewChange = + viewModelChanges + .CombineLatest( + contractChanges, + static (vm, contract) => new { ViewModel = vm, Contract = contract }) + .Where(static x => x.ViewModel is not null); + + var defaultViewChange = + viewModelChanges + .CombineLatest( + defaultContentChanges, + static (vm, defaultContent) => new { ViewModel = vm, DefaultContent = defaultContent }) + .Where(static x => x.ViewModel is null && x.DefaultContent is not null) + .Select(static x => x.DefaultContent); + + _subscriptions.Add( + viewChange + .ObserveOn(RxSchedulers.MainThreadScheduler) + .Subscribe( + x => { - var message = $"Unable to resolve view for \"{x.ViewModel?.GetType()}\""; + var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; + var view = viewLocator.ResolveView(x.ViewModel, x.Contract); - if (x.Contract is not null) + if (view is null) { - message += $" and contract \"{x.Contract.GetType()}\""; + var message = $"Unable to resolve view for \"{x.ViewModel?.GetType()}\""; + + if (x.Contract is not null) + { + message += $" and contract \"{x.Contract.GetType()}\""; + } + + message += "."; + throw new Exception(message); } - message += "."; - throw new Exception(message); - } + if (view is not NSViewController viewController) + { + //// TODO: As viewController may be NULL at this point this execution will never show the FullName, find fixed text to replace this with. + throw new Exception($"Resolved view type '{view?.GetType().FullName}' is not a '{typeof(NSViewController).FullName}'."); + } - if (view is not NSViewController viewController) - { - //// TODO: As viewController may be NULL at this point this execution will never show the FullName, find fixed text to replace this with. + view.ViewModel = x.ViewModel; + Adopt(this, viewController); + + _currentView.Disposable = + new CompositeDisposable( + viewController, + Disposable.Create(() => Disown(viewController))); + })); + + _subscriptions.Add( + defaultViewChange + .ObserveOn(RxSchedulers.MainThreadScheduler) + .Subscribe(x => Adopt(this, x))); + } - throw new Exception($"Resolved view type '{view?.GetType().FullName}' is not a '{typeof(NSViewController).FullName}'."); - } + /// + /// Creates a contract stream that (1) emits an initial value, (2) subscribes to the current + /// , and (3) swaps the inner subscription when the property changes. + /// + /// An observable of view contracts. + private IObservable CreateViewContractStream() + { + return Observable.Create( + observer => + { + // Preserve the previous StartWith((string?)null) semantics. + observer.OnNext(null); - view.ViewModel = x.ViewModel; - Adopt(this, viewController); + void SwapInner(IObservable? source) + { + _viewContractObservableSubscription.Disposable = + source is null + ? Disposable.Empty + : source.Subscribe(observer); + } + + // Subscribe to the initial observable (if any). + SwapInner(ViewContractObservable); + + // Listen for property changes and rewire the inner subscription. + var outerSubscription = + Changed + .Where(static e => e.PropertyName == nameof(ViewContractObservable)) + .Subscribe(_ => SwapInner(ViewContractObservable)); + + return new CompositeDisposable(outerSubscription); + }); + } - _currentView.Disposable = (CompositeDisposable?)[viewController, Disposable.Create(() => Disown(viewController))]; - }); + /// + /// Observes changes to a property without using WhenAny* APIs (avoids RUC/RDC from expression-based pipelines). + /// The observable emits the current value immediately and then emits on each subsequent property change. + /// + /// The property type. + /// A getter for the property value. + /// The name of the property to observe. + /// An observable that emits the property value. + private IObservable ObserveProperty(Func getter, string propertyName) + { + return Observable.Create( + observer => + { + observer.OnNext(getter(this)); + + return Changed + .Where(e => e.PropertyName == propertyName) + .Select(_ => getter(this)) + .Subscribe(observer); + }); + } - defaultViewChange - .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(x => Adopt(this, x)); + /// + /// Updates the backing field and raises property changed notifications. + /// + /// The new contract value. + private void SetViewContract(string? contract) + { + this.RaiseAndSetIfChanged(ref _viewContract, contract, nameof(ViewContract)); } } diff --git a/src/ReactiveUI/Platforms/ios/UIKitCommandBinders.cs b/src/ReactiveUI/Platforms/ios/UIKitCommandBinders.cs index 3ee442e20e..a0a6447c9c 100644 --- a/src/ReactiveUI/Platforms/ios/UIKitCommandBinders.cs +++ b/src/ReactiveUI/Platforms/ios/UIKitCommandBinders.cs @@ -3,6 +3,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#nullable enable + using System.Reflection; using UIKit; @@ -12,26 +14,66 @@ namespace ReactiveUI; /// /// UI Kit command binder platform registrations. /// -/// [Preserve(AllMembers = true)] -[RequiresUnreferencedCode("UIKitCommandBinders uses methods that may require unreferenced code")] -[RequiresDynamicCode("UIKitCommandBinders uses methods that may require unreferenced code")] -public class UIKitCommandBinders : FlexibleCommandBinder +public sealed class UIKitCommandBinders : FlexibleCommandBinder { - private const string Enabled = nameof(Enabled); + /// + /// The reflected property name used to control enabled state across UIKit types. + /// + private const string EnabledPropertyName = "Enabled"; + + /// + /// Cached for . + /// + private static readonly PropertyInfo UIControlEnabledProperty = + typeof(UIControl).GetRuntimeProperty(EnabledPropertyName) ?? + throw new InvalidOperationException("There is no Enabled property on UIControl which is needed for binding."); + + /// + /// Cached for . + /// + private static readonly PropertyInfo UIRefreshControlEnabledProperty = + typeof(UIRefreshControl).GetRuntimeProperty(EnabledPropertyName) ?? + throw new InvalidOperationException("There is no Enabled property on UIRefreshControl which is needed for binding."); + + /// + /// Cached for . + /// + private static readonly PropertyInfo UIBarButtonItemEnabledProperty = + typeof(UIBarButtonItem).GetRuntimeProperty(EnabledPropertyName) ?? + throw new InvalidOperationException("There is no Enabled property on UIBarButtonItem which is needed for binding."); /// /// Initializes a new instance of the class. /// public UIKitCommandBinders() { - Register(typeof(UIControl), 9, static (cmd, t, cp) => ForTargetAction(cmd, t, cp, typeof(UIControl).GetRuntimeProperty(Enabled) ?? throw new InvalidOperationException("There is no Enabled property on the UIControl which is needed for binding."))); - Register(typeof(UIRefreshControl), 10, static (cmd, t, cp) => ForEvent(cmd, t, cp, "ValueChanged", typeof(UIRefreshControl).GetRuntimeProperty(Enabled) ?? throw new InvalidOperationException("There is no Enabled property on the UIRefreshControl which is needed for binding."))); - Register(typeof(UIBarButtonItem), 10, static (cmd, t, cp) => ForEvent(cmd, t, cp, "Clicked", typeof(UIBarButtonItem).GetRuntimeProperty(Enabled) ?? throw new InvalidOperationException("There is no Enabled property on the UIBarButtonItem which is needed for binding."))); + // UIControl: prefer the AOT-safe target-action helper (no string event name). + Register(typeof(UIControl), 9, static (cmd, t, cp) => ForTargetAction(cmd, t, cp, UIControlEnabledProperty)); + + // UIRefreshControl: ValueChanged is a .NET event; use the AOT-safe ForEvent overload via add/remove delegates. + Register(typeof(UIRefreshControl), 10, (cmd, t, cp) => + ForEvent( + cmd, + (UIRefreshControl)t!, + cp, + addHandler: h => ((UIRefreshControl)t!).ValueChanged += h, // see note below + removeHandler: h => ((UIRefreshControl)t!).ValueChanged -= h, + UIRefreshControlEnabledProperty)); + + // UIBarButtonItem: Clicked is a .NET event; use the AOT-safe ForEvent overload via add/remove delegates. + Register(typeof(UIBarButtonItem), 10, (cmd, t, cp) => + ForEvent( + cmd, + (UIBarButtonItem)t!, + cp, + addHandler: h => ((UIBarButtonItem)t!).Clicked += h, + removeHandler: h => ((UIBarButtonItem)t!).Clicked -= h, + UIBarButtonItemEnabledProperty)); } /// - /// Gets the UIKitCommandBinders instance. + /// Gets a lazily-initialized singleton instance of . /// public static Lazy Instance { get; } = new(); } diff --git a/src/ReactiveUI/Platforms/ios/UIKitObservableForProperty.cs b/src/ReactiveUI/Platforms/ios/UIKitObservableForProperty.cs index b944c2bccd..d99152b9a9 100644 --- a/src/ReactiveUI/Platforms/ios/UIKitObservableForProperty.cs +++ b/src/ReactiveUI/Platforms/ios/UIKitObservableForProperty.cs @@ -8,43 +8,106 @@ namespace ReactiveUI; /// -/// UIKitObservableForProperty is an object that knows how to -/// create notifications for a given type of object. Implement this if you -/// are porting RxUI to a new UI toolkit, or generally want to enable WhenAny -/// for another type of object that can be observed in a unique way. +/// UIKitObservableForProperty provides toolkit-specific observable factories used by ReactiveUI +/// to generate change notifications for UIKit controls in WhenAny* and related operators. /// +/// +/// This implementation registers observable factories for common UIKit properties that change via +/// control events or notifications. +/// +/// For event-based notifications, this implementation uses explicit add/remove handler overloads +/// (non-reflection) provided by to improve performance and +/// trimming/AOT compatibility. +/// [Preserve] -public class UIKitObservableForProperty : ObservableForPropertyBase +public sealed class UIKitObservableForProperty : ObservableForPropertyBase { /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("UIKitObservableForProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("UIKitObservableForProperty uses methods that may require unreferenced code")] -#endif public UIKitObservableForProperty() { - Register(typeof(UIControl), "Value", 20, static (s, p) => ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged)); - Register(typeof(UITextField), "Text", 30, static (s, p) => ObservableFromNotification(s, p, UITextField.TextFieldTextDidChangeNotification)); - Register(typeof(UITextView), "Text", 30, static (s, p) => ObservableFromNotification(s, p, UITextView.TextDidChangeNotification)); - Register(typeof(UIDatePicker), "Date", 30, static (s, p) => ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged)); - Register(typeof(UISegmentedControl), "SelectedSegment", 30, static (s, p) => ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged)); - Register(typeof(UISwitch), "On", 30, static (s, p) => ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged)); - Register(typeof(UISegmentedControl), "SelectedSegment", 30, static (s, p) => ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged)); - - // Warning: This will stomp the Control's delegate - Register(typeof(UITabBar), "SelectedItem", 30, static (s, p) => ObservableFromEvent(s, p, "ItemSelected")); - - // Warning: This will stomp the Control's delegate - Register(typeof(UISearchBar), "Text", 30, static (s, p) => ObservableFromEvent(s, p, "TextChanged")); + // UIControl "Value" changes via ValueChanged. + Register( + typeof(UIControl), + "Value", + affinity: 20, + static (sender, expr) => ObservableFromUIControlEvent(sender, expr, UIControlEvent.ValueChanged)); + + // UITextField "Text" changes via notification. + Register( + typeof(UITextField), + "Text", + affinity: 30, + static (sender, expr) => ObservableFromNotification(sender, expr, UITextField.TextFieldTextDidChangeNotification)); + + // UITextView "Text" changes via notification. + Register( + typeof(UITextView), + "Text", + affinity: 30, + static (sender, expr) => ObservableFromNotification(sender, expr, UITextView.TextDidChangeNotification)); + + // UIDatePicker "Date" changes via ValueChanged. + Register( + typeof(UIDatePicker), + "Date", + affinity: 30, + static (sender, expr) => ObservableFromUIControlEvent(sender, expr, UIControlEvent.ValueChanged)); + + // UISegmentedControl "SelectedSegment" changes via ValueChanged. + Register( + typeof(UISegmentedControl), + "SelectedSegment", + affinity: 30, + static (sender, expr) => ObservableFromUIControlEvent(sender, expr, UIControlEvent.ValueChanged)); + + // UISwitch "On" changes via ValueChanged. + Register( + typeof(UISwitch), + "On", + affinity: 30, + static (sender, expr) => ObservableFromUIControlEvent(sender, expr, UIControlEvent.ValueChanged)); + + // Warning: This event-based approach may change the control's behavior depending on external delegate usage. + // Use explicit add/remove to avoid reflection and trimming hazards. + Register( + typeof(UITabBar), + "SelectedItem", + affinity: 30, + (sender, expr) => + { + var tabBar = (UITabBar)sender; + return ObservableFromEvent( + tabBar, + expr, + addHandler: h => tabBar.ItemSelected += h, + removeHandler: h => tabBar.ItemSelected -= h); + }); + + // Warning: This event-based approach may change the control's behavior depending on external delegate usage. + // Use explicit add/remove to avoid reflection and trimming hazards. + Register( + typeof(UISearchBar), + "Text", + affinity: 30, + (sender, expr) => + { + var searchBar = (UISearchBar)sender; + return ObservableFromEvent( + searchBar, + expr, + addHandler: h => searchBar.TextChanged += h, + removeHandler: h => searchBar.TextChanged -= h); + }); } /// - /// Gets the UI Kit ObservableForProperty instance. + /// Gets the shared instance. /// -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Deliberate>")] -#endif + /// + /// The instance is created lazily. Consumers typically register it with the service locator once + /// during application initialization. + /// public static Lazy Instance { get; } = new(); } diff --git a/src/ReactiveUI/Platforms/mac/AutoSuspendHelper.cs b/src/ReactiveUI/Platforms/mac/AutoSuspendHelper.cs index dd6ad2aa58..926cbc488b 100644 --- a/src/ReactiveUI/Platforms/mac/AutoSuspendHelper.cs +++ b/src/ReactiveUI/Platforms/mac/AutoSuspendHelper.cs @@ -3,6 +3,10 @@ // 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; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + using AppKit; using Foundation; @@ -10,8 +14,9 @@ namespace ReactiveUI; /// -/// Bridges lifecycle notifications into on macOS. +/// Bridges lifecycle notifications into on macOS. /// +/// The concrete type. /// /// /// Instantiate this helper inside your to map DidFinishLaunching, @@ -30,8 +35,8 @@ namespace ReactiveUI; /// public override void DidFinishLaunching(NSNotification notification) /// { /// _suspensionHelper ??= new AutoSuspendHelper(this); -/// RxApp.SuspensionHost.CreateNewAppState = () => new ShellState(); -/// RxApp.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(AppStatePathProvider.Resolve())); +/// RxSuspension.SuspensionHost.CreateNewAppState = () => new ShellState(); +/// RxSuspension.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(AppStatePathProvider.Resolve())); /// base.DidFinishLaunching(notification); /// } /// } @@ -39,99 +44,143 @@ namespace ReactiveUI; /// /// /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("AutoSuspendHelper uses RxApp properties which require dynamic code generation")] -[RequiresUnreferencedCode("AutoSuspendHelper uses RxApp properties which may require unreferenced code")] -#endif -public class AutoSuspendHelper : IEnableLogger, IDisposable +public class AutoSuspendHelper<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T> : IEnableLogger, IDisposable + where T : NSApplicationDelegate { + /// + /// Emits disposables used by subscribers to delimit persistence work. + /// private readonly Subject _shouldPersistState = new(); + + /// + /// Emits values to indicate the application is resuming from a prior persisted state. + /// private readonly Subject _isResuming = new(); + + /// + /// Emits values to indicate the application is becoming active again after being backgrounded/hidden. + /// private readonly Subject _isUnpausing = new(); + /// + /// Emits values to indicate an unexpected termination, prompting state invalidation. + /// + private readonly Subject _untimelyDemise = new(); + + /// + /// Cached handler so we can unsubscribe during . + /// + private readonly UnhandledExceptionEventHandler _unhandledExceptionHandler; + + /// + /// Tracks whether this instance has been disposed. + /// private bool _isDisposed; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The application delegate. - public AutoSuspendHelper(NSApplicationDelegate appDelegate) + /// + /// Thrown when is . + /// + /// + /// Thrown when required lifecycle methods are not declared on 's runtime type. + /// + public AutoSuspendHelper(T appDelegate) { - Reflection.ThrowIfMethodsNotOverloaded( - nameof(AutoSuspendHelper), - appDelegate, - nameof(ApplicationShouldTerminate), - nameof(DidFinishLaunching), - nameof(DidResignActive), - nameof(DidBecomeActive), - nameof(DidHide)); - - RxApp.SuspensionHost.IsLaunchingNew = Observable.Never; - RxApp.SuspensionHost.IsResuming = _isResuming; - RxApp.SuspensionHost.IsUnpausing = _isUnpausing; - RxApp.SuspensionHost.ShouldPersistState = _shouldPersistState; - - var untimelyDemise = new Subject(); - AppDomain.CurrentDomain.UnhandledException += (o, e) => - untimelyDemise.OnNext(Unit.Default); - - RxApp.SuspensionHost.ShouldInvalidateState = untimelyDemise; + ArgumentNullException.ThrowIfNull(appDelegate); + + // Developer-time guard. Cache the result per delegate runtime type to avoid repeated reflection. + EnsureMethodsNotOverloadedCached(); + + RxSuspension.SuspensionHost.IsLaunchingNew = Observable.Never; + RxSuspension.SuspensionHost.IsResuming = _isResuming; + RxSuspension.SuspensionHost.IsUnpausing = _isUnpausing; + RxSuspension.SuspensionHost.ShouldPersistState = _shouldPersistState; + + // Keep a stable delegate instance so we can unsubscribe on Dispose. + _unhandledExceptionHandler = (_, _) => _untimelyDemise.OnNext(Unit.Default); + AppDomain.CurrentDomain.UnhandledException += _unhandledExceptionHandler; + + RxSuspension.SuspensionHost.ShouldInvalidateState = _untimelyDemise; } /// - /// Applications the should terminate. + /// Handles the application termination request. /// - /// The sender. - /// The termination reply from the application. + /// The application instance requesting termination. + /// + /// to delay termination until persistence completes. + /// /// - /// Delays the OS shutdown until subscribers finish writing + /// Delays OS shutdown until subscribers finish writing /// , replying with /// once persistence completes. /// public NSApplicationTerminateReply ApplicationShouldTerminate(NSApplication sender) { + ThrowIfDisposed(); + + // Ensure the persist notification is emitted on the main thread, as callers typically interact with AppKit. RxSchedulers.MainThreadScheduler.Schedule(() => - _shouldPersistState.OnNext(Disposable.Create(() => - sender.ReplyToApplicationShouldTerminate(true)))); + _shouldPersistState.OnNext( + Disposable.Create(() => sender.ReplyToApplicationShouldTerminate(true)))); return NSApplicationTerminateReply.Later; } /// - /// Did finish launching. + /// Notifies the helper that the application finished launching. /// - /// The notification. + /// The launch notification. /// /// Signals so state drivers load the last persisted . /// - public void DidFinishLaunching(NSNotification notification) => _isResuming.OnNext(Unit.Default); + public void DidFinishLaunching(NSNotification notification) + { + ThrowIfDisposed(); + _isResuming.OnNext(Unit.Default); + } /// - /// Did resign active. + /// Notifies the helper that the application resigned active state. /// - /// The notification. + /// The resign-active notification. /// /// Requests an asynchronous save by emitting via . /// - public void DidResignActive(NSNotification notification) => _shouldPersistState.OnNext(Disposable.Empty); + public void DidResignActive(NSNotification notification) + { + ThrowIfDisposed(); + _shouldPersistState.OnNext(Disposable.Empty); + } /// - /// Did become active. + /// Notifies the helper that the application became active. /// - /// The notification. + /// The become-active notification. /// /// Signals so subscribers can refresh transient UI when the app regains focus. /// - public void DidBecomeActive(NSNotification notification) => _isUnpausing.OnNext(Unit.Default); + public void DidBecomeActive(NSNotification notification) + { + ThrowIfDisposed(); + _isUnpausing.OnNext(Unit.Default); + } /// - /// Did hide. + /// Notifies the helper that the application was hidden. /// - /// The notification. + /// The hide notification. /// /// Initiates a quick save when the app is hidden, mirroring the behavior of . /// - public void DidHide(NSNotification notification) => _shouldPersistState.OnNext(Disposable.Empty); + public void DidHide(NSNotification notification) + { + ThrowIfDisposed(); + _shouldPersistState.OnNext(Disposable.Empty); + } /// public void Dispose() @@ -141,9 +190,9 @@ public void Dispose() } /// - /// Disposes of resources inside the class. + /// Releases resources held by this helper. /// - /// If we are disposing managed resources. + /// If , disposes managed resources. protected virtual void Dispose(bool isDisposing) { if (_isDisposed) @@ -151,13 +200,104 @@ protected virtual void Dispose(bool isDisposing) return; } - if (isDisposing) + _isDisposed = true; + + if (!isDisposing) { - _isResuming?.Dispose(); - _isUnpausing?.Dispose(); - _shouldPersistState?.Dispose(); + return; } - _isDisposed = true; + // Unsubscribe first to avoid keeping this instance alive via the AppDomain event. + AppDomain.CurrentDomain.UnhandledException -= _unhandledExceptionHandler; + + _isResuming.Dispose(); + _isUnpausing.Dispose(); + _shouldPersistState.Dispose(); + _untimelyDemise.Dispose(); + } + + /// + /// Performs the "methods must be implemented" guard once per application delegate runtime type. + /// + /// + /// Thrown when required lifecycle methods are not declared on appDelegate's runtime type. + /// + /// + /// Delegates to on a cache miss, + /// but avoids repeating the reflection scan for each helper construction. + /// + private static void EnsureMethodsNotOverloadedCached() + { + var type = typeof(T); + + if (MethodForwardingValidationCache.IsValidated(type)) + { + return; + } + + Reflection.ThrowIfMethodsNotOverloaded( + nameof(AutoSuspendHelper), + type, + nameof(ApplicationShouldTerminate), + nameof(DidFinishLaunching), + nameof(DidResignActive), + nameof(DidBecomeActive), + nameof(DidHide)); + + MethodForwardingValidationCache.MarkValidated(type); + } + + /// + /// Throws if this instance has been disposed. + /// + /// Thrown when the instance is disposed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ThrowIfDisposed() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(AutoSuspendHelper)); + } + } + + /// + /// Stores a process-wide cache of which delegate types have been validated for lifecycle forwarding. + /// + /// + /// Uses a single gate because this is cold-path initialization and the number of delegate types is tiny. + /// + private static class MethodForwardingValidationCache + { +#if NET9_0_OR_GREATER + private static readonly Lock Gate = new(); +#else + private static readonly object Gate = new(); +#endif + private static readonly Dictionary Validated = new(); + + /// + /// Returns whether the specified delegate type has been validated. + /// + /// The delegate runtime type. + /// if the type has been validated; otherwise . + public static bool IsValidated(Type type) + { + lock (Gate) + { + return Validated.ContainsKey(type); + } + } + + /// + /// Marks the specified delegate type as validated. + /// + /// The delegate runtime type. + public static void MarkValidated(Type type) + { + lock (Gate) + { + Validated[type] = 0; + } + } } } diff --git a/src/ReactiveUI/Platforms/mac/PlatformRegistrations.cs b/src/ReactiveUI/Platforms/mac/PlatformRegistrations.cs index a04b8590d0..30742a46de 100644 --- a/src/ReactiveUI/Platforms/mac/PlatformRegistrations.cs +++ b/src/ReactiveUI/Platforms/mac/PlatformRegistrations.cs @@ -12,22 +12,28 @@ namespace ReactiveUI; public class PlatformRegistrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Platform registration uses ComponentModelTypeConverter and RxApp which require dynamic code generation")] - [RequiresUnreferencedCode("Platform registration uses ComponentModelTypeConverter and RxApp which may require unreferenced code")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); - registerFunction(static () => new PlatformOperations(), typeof(IPlatformOperations)); - registerFunction(static () => new ComponentModelTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new AppKitObservableForProperty(), typeof(ICreatesObservableForProperty)); - registerFunction(static () => new TargetActionCommandBinder(), typeof(ICreatesCommandBinding)); - registerFunction(static () => new DateTimeNSDateConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new KVOObservableForProperty(), typeof(ICreatesObservableForProperty)); + registrar.RegisterConstant(static () => new PlatformOperations()); + registrar.RegisterConstant(static () => new ComponentModelFallbackConverter()); + registrar.RegisterConstant(static () => new AppKitObservableForProperty()); + registrar.RegisterConstant(static () => new TargetActionCommandBinder()); + + // DateTime ↔ NSDate converters + registrar.RegisterConstant(static () => new DateTimeToNSDateConverter()); + registrar.RegisterConstant(static () => new NullableDateTimeToNSDateConverter()); + registrar.RegisterConstant(static () => new NSDateToDateTimeConverter()); + registrar.RegisterConstant(static () => new NSDateToNullableDateTimeConverter()); + + // DateTimeOffset ↔ NSDate converters + registrar.RegisterConstant(static () => new DateTimeOffsetToNSDateConverter()); + registrar.RegisterConstant(static () => new NullableDateTimeOffsetToNSDateConverter()); + registrar.RegisterConstant(static () => new NSDateToDateTimeOffsetConverter()); + registrar.RegisterConstant(static () => new NSDateToNullableDateTimeOffsetConverter()); + + registrar.RegisterConstant(static () => new KVOObservableForProperty()); if (!ModeDetector.InUnitTestRunner()) { @@ -35,6 +41,6 @@ public void Register(Action, Type> registerFunction) RxSchedulers.MainThreadScheduler = new WaitForDispatcherScheduler(static () => new NSRunloopScheduler()); } - registerFunction(static () => new AppSupportJsonSuspensionDriver(), typeof(ISuspensionDriver)); + registrar.RegisterConstant(static () => new AppSupportJsonSuspensionDriver()); } } diff --git a/src/ReactiveUI/Platforms/mac/ReactiveWindowController.cs b/src/ReactiveUI/Platforms/mac/ReactiveWindowController.cs index bdf7f171e4..a91e543e96 100644 --- a/src/ReactiveUI/Platforms/mac/ReactiveWindowController.cs +++ b/src/ReactiveUI/Platforms/mac/ReactiveWindowController.cs @@ -13,10 +13,6 @@ namespace ReactiveUI; /// This is a NSWindowController that is both a NSWindowController and has ReactiveObject powers /// (i.e. you can call RaiseAndSetIfChanged). /// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveWindowController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveWindowController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public class ReactiveWindowController : NSWindowController, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { private readonly Subject _activated = new(); diff --git a/src/ReactiveUI/Platforms/mobile-common/ComponentModelFallbackConverter.cs b/src/ReactiveUI/Platforms/mobile-common/ComponentModelFallbackConverter.cs new file mode 100644 index 0000000000..6b650b18ec --- /dev/null +++ b/src/ReactiveUI/Platforms/mobile-common/ComponentModelFallbackConverter.cs @@ -0,0 +1,155 @@ +// Copyright (c) 2025 .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.Concurrent; + +namespace ReactiveUI; + +/// +/// Fallback converter using System.ComponentModel.TypeDescriptor for reflection-based type conversion. +/// This converter is consulted only when no typed converter matches. +/// +/// +/// +/// This converter uses reflection and is not AOT-safe. It should be used as a last resort +/// when no typed converter can handle the conversion. +/// +/// +/// The converter caches component model capability lookups to avoid repeated expensive +/// reflection operations. +/// +/// +[Preserve(AllMembers = true)] +public sealed class ComponentModelFallbackConverter : IBindingFallbackConverter +{ + /// + /// Cache of resolved component model converters for specific (from,to) pairs. + /// + /// + /// This is a stable cache because type metadata tends to be reused and eviction causes repeated component model lookup. + /// + private static readonly ConcurrentDictionary<(Type From, Type To), TypeConverter?> _converterCache = new(); + + /// + /// Cache of component model capability lookups (whether conversion is supported). + /// + private static readonly ConcurrentDictionary<(Type From, Type To), bool> _capabilityCache = new(); + + /// + [UnconditionalSuppressMessage( + "ReflectionAnalysis", + "IL2026:RequiresUnreferencedCode", + Justification = "The callers of this method ensure getting the converter is trim compatible - i.e. the type is not Nullable.")] + public int GetAffinityForObjects([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType) + { + ArgumentExceptionHelper.ThrowIfNull(fromType); + ArgumentExceptionHelper.ThrowIfNull(toType); + + // Return cached capability or compute and cache + var canConvert = _capabilityCache.GetOrAdd((fromType, toType), static key => + { + try + { + // Check if component model can convert this pair + // String is a special case - use the target type's converter for string→T conversions + var (lookupFrom, lookupTo) = key.From == typeof(string) ? (key.To, key.From) : (key.From, key.To); + var converter = TypeDescriptor.GetConverter(lookupFrom); + return converter?.CanConvertTo(lookupTo) == true; + } + catch + { + // Component model threw - assume cannot convert + return false; + } + }); + + // Affinity 1 = last resort (only when nothing else works) + return canConvert ? 1 : 0; + } + + /// + [UnconditionalSuppressMessage( + "ReflectionAnalysis", + "IL2026:RequiresUnreferencedCode", + Justification = "The callers of this method ensure getting the converter is trim compatible - i.e. the type is not Nullable.")] + public bool TryConvert([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, object from, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType, object? conversionHint, [NotNullWhen(true)] out object? result) + { + ArgumentExceptionHelper.ThrowIfNull(from); + ArgumentExceptionHelper.ThrowIfNull(toType); + + try + { + // Get or create cached converter for this pair + var converter = GetConverter(fromType, toType); + if (converter is null) + { + this.Log().Debug("Component model cannot convert {FromType} to {ToType}", fromType, toType); + result = null; + return false; + } + + // Perform conversion + // String is a special case: use ConvertFrom for string→T conversions + var converted = (fromType == typeof(string)) + ? converter.ConvertFrom(from) + : converter.ConvertTo(from, toType); + + if (converted is not null) + { + result = converted; + return true; + } + + // Conversion returned null - treat as failure + result = null; + return false; + } + catch (FormatException ex) + { + // Format exception = conversion failed (expected for invalid input) + this.Log().Debug(ex, "Component model conversion failed (FormatException) for {FromType} -> {ToType}", fromType, toType); + result = null; + return false; + } + catch (Exception ex) when (ex.InnerException is FormatException or IndexOutOfRangeException) + { + // Component model often wraps format exceptions + this.Log().Debug(ex, "Component model conversion failed (wrapped exception) for {FromType} -> {ToType}", fromType, toType); + result = null; + return false; + } + catch (Exception ex) + { + // Unexpected exception - log as warning and treat as failure + this.Log().Warn(ex, "Component model conversion threw unexpected exception for {FromType} -> {ToType}", fromType, toType); + result = null; + return false; + } + } + + /// + /// Resolves a component model for the specified pair. + /// + /// The source type. + /// The target type. + /// + /// A converter instance if component model supports the conversion; otherwise . + /// + [UnconditionalSuppressMessage( + "ReflectionAnalysis", + "IL2026:RequiresUnreferencedCode", + Justification = "The callers of this method ensure getting the converter is trim compatible - i.e. the type is not Nullable.")] + private static TypeConverter? GetConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType) + { + // Component model special-case: string conversion uses the target's converter for ConvertFrom(string). + var (lookupFrom, lookupTo) = fromType == typeof(string) ? (toType, fromType) : (fromType, toType); + + return _converterCache.GetOrAdd((fromType, toType), _ => + { + var converter = TypeDescriptor.GetConverter(lookupFrom); + return converter?.CanConvertTo(lookupTo) == true ? converter : null; + }); + } +} diff --git a/src/ReactiveUI/Platforms/mobile-common/ComponentModelTypeConverter.cs b/src/ReactiveUI/Platforms/mobile-common/ComponentModelTypeConverter.cs deleted file mode 100644 index 1f7aaef8c0..0000000000 --- a/src/ReactiveUI/Platforms/mobile-common/ComponentModelTypeConverter.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2025 .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; - -/// -/// The component model binding type converter. -/// -[Preserve(AllMembers = true)] -public class ComponentModelTypeConverter : IBindingTypeConverter -{ - [SuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "TypeDescriptor is safe to use here as we are not using reflection ourselves")] - [SuppressMessage("AOT", "IL2077:Members annotated with 'RequiresDynamicCodeAttribute' require dynamic access otherwise can break functionality when ahead-of-time (AOT) compiling application code", Justification = "TypeDescriptor is safe to use here as we are not using dynamic code ourselves")] - private readonly MemoizingMRUCache<(Type fromType, Type toType), TypeConverter?> _typeConverterCache = new( - static (types, _) => - { - // NB: String is a Magical Type(tm) to TypeConverters. If we are - // converting from string => int, we need the Int converter, not - // the string converter :-/ - if (types.fromType == typeof(string)) - { - types = (types.toType, types.fromType); - } - - var converter = TypeDescriptor.GetConverter(types.fromType); - return converter.CanConvertTo(types.toType) ? converter : null; - }, - RxApp.SmallCacheLimit); - - /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - var converter = _typeConverterCache.Get((fromType, toType)); - return converter is not null ? 10 : 0; - } - - /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result) - { - if (from is null) - { - result = null; - return true; - } - - var fromType = from.GetType(); - var converter = _typeConverterCache.Get((fromType, toType)) ?? throw new ArgumentException($"Can't convert {fromType} to {toType}. To fix this, register a IBindingTypeConverter"); - try - { - // TODO: This should use conversionHint to determine whether this is locale-aware or not - result = (fromType == typeof(string)) ? - converter.ConvertFrom(from) : converter.ConvertTo(from, toType); - - return true; - } - catch (FormatException) - { - result = null; - return false; - } - catch (Exception e) - { - // Errors from ConvertFrom end up here but wrapped in - // outer exception. Add more types here as required. - // IndexOutOfRangeException is given when trying to - // convert empty strings with some/all? converters - if (e.InnerException is IndexOutOfRangeException || - e.InnerException is FormatException) - { - result = null; - return false; - } - - throw new Exception($"Can't convert from {from.GetType()} to {toType}.", e); - } - } -} diff --git a/src/ReactiveUI/Platforms/net/ComponentModelFallbackConverter.cs b/src/ReactiveUI/Platforms/net/ComponentModelFallbackConverter.cs new file mode 100644 index 0000000000..83785e4f08 --- /dev/null +++ b/src/ReactiveUI/Platforms/net/ComponentModelFallbackConverter.cs @@ -0,0 +1,151 @@ +// Copyright (c) 2025 .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.Concurrent; + +namespace ReactiveUI; + +/// +/// Fallback converter using System.ComponentModel.TypeDescriptor for reflection-based type conversion. +/// This converter is consulted only when no typed converter matches. +/// +/// +/// +/// This converter uses reflection and is not AOT-safe. It should be used as a last resort +/// when no typed converter can handle the conversion. +/// +/// +/// The converter caches component model capability lookups to avoid repeated expensive +/// reflection operations. +/// +/// +public sealed class ComponentModelFallbackConverter : IBindingFallbackConverter +{ + /// + /// Cache of resolved component model converters for specific (from,to) pairs. + /// + /// + /// This is a stable cache because type metadata tends to be reused and eviction causes repeated component model lookup. + /// + private static readonly ConcurrentDictionary<(Type From, Type To), TypeConverter?> _converterCache = new(); + + /// + /// Cache of component model capability lookups (whether conversion is supported). + /// + private static readonly ConcurrentDictionary<(Type From, Type To), bool> _capabilityCache = new(); + + /// + [UnconditionalSuppressMessage( + "ReflectionAnalysis", + "IL2026:RequiresUnreferencedCode", + Justification = "The callers of this method ensure getting the converter is trim compatible - i.e. the type is not Nullable.")] + public int GetAffinityForObjects([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType) + { + ArgumentExceptionHelper.ThrowIfNull(fromType); + ArgumentExceptionHelper.ThrowIfNull(toType); + + // Return cached capability or compute and cache + var canConvert = _capabilityCache.GetOrAdd((fromType, toType), static key => + { + try + { + // Check if component model can convert this pair + // String is a special case - use the target type's converter for string→T conversions + var (lookupFrom, lookupTo) = key.From == typeof(string) ? (key.To, key.From) : (key.From, key.To); + var converter = TypeDescriptor.GetConverter(lookupFrom); + return converter?.CanConvertTo(lookupTo) == true; + } + catch + { + // Component model threw - assume cannot convert + return false; + } + }); + + // Affinity 1 = last resort (only when nothing else works) + return canConvert ? 1 : 0; + } + + /// + public bool TryConvert([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, object from, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType, object? conversionHint, [NotNullWhen(true)] out object? result) + { + ArgumentExceptionHelper.ThrowIfNull(fromType); + ArgumentExceptionHelper.ThrowIfNull(from); + ArgumentExceptionHelper.ThrowIfNull(toType); + + try + { + // Get or create cached converter for this pair + var converter = GetConverter(fromType, toType); + if (converter is null) + { + this.Log().Debug("Component model cannot convert {FromType} to {ToType}", fromType, toType); + result = null; + return false; + } + + // Perform conversion + // String is a special case: use ConvertFrom for string→T conversions + var converted = (fromType == typeof(string)) + ? converter.ConvertFrom(from) + : converter.ConvertTo(from, toType); + + if (converted is not null) + { + result = converted; + return true; + } + + // Conversion returned null - treat as failure + result = null; + return false; + } + catch (FormatException ex) + { + // Format exception = conversion failed (expected for invalid input) + this.Log().Debug(ex, "Component model conversion failed (FormatException) for {FromType} -> {ToType}", fromType, toType); + result = null; + return false; + } + catch (Exception ex) when (ex.InnerException is FormatException or IndexOutOfRangeException) + { + // Component model often wraps format exceptions + this.Log().Debug(ex, "Component model conversion failed (wrapped exception) for {FromType} -> {ToType}", fromType, toType); + result = null; + return false; + } + catch (Exception ex) + { + // Unexpected exception - log as warning and treat as failure + this.Log().Warn(ex, "Component model conversion threw unexpected exception for {FromType} -> {ToType}", fromType, toType); + result = null; + return false; + } + } + + /// + /// Resolves a component model for the specified pair. + /// + /// The source type. + /// The target type. + /// + /// A converter instance if component model supports the conversion; otherwise . + /// + [UnconditionalSuppressMessage( + "ReflectionAnalysis", + "IL2026:RequiresUnreferencedCode", + Justification = "The callers of this method ensure getting the converter is trim compatible - i.e. the type is not Nullable.")] + private static TypeConverter? GetConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type toType) + { + // Component model special-case: string conversion uses the target's converter for ConvertFrom(string). + var (lookupFrom, lookupTo) = fromType == typeof(string) ? (toType, fromType) : (fromType, toType); + + return _converterCache.GetOrAdd((fromType, toType), _ => + { + var converter = TypeDescriptor.GetConverter(lookupFrom); + return converter?.CanConvertTo(lookupTo) == true ? converter : null; + }); + } +} diff --git a/src/ReactiveUI/Platforms/net/ComponentModelTypeConverter.cs b/src/ReactiveUI/Platforms/net/ComponentModelTypeConverter.cs deleted file mode 100644 index 80eff99045..0000000000 --- a/src/ReactiveUI/Platforms/net/ComponentModelTypeConverter.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2022 .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. -// - -#nullable enable -using System.Diagnostics.CodeAnalysis; - -namespace ReactiveUI; - -/// -/// Binding Type Converter for component model. -/// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("Component model type conversion uses reflection and dynamic code generation")] -[RequiresUnreferencedCode("Component model type conversion may reference types that could be trimmed")] -#endif -public class ComponentModelTypeConverter : IBindingTypeConverter -{ - private readonly MemoizingMRUCache<(Type fromType, Type toType), TypeConverter?> _typeConverterCache = new ( - (types, _) => - { - // NB: String is a Magical Type(tm) to TypeConverters. If we are - // converting from string => int, we need the Int converter, not - // the string converter :-/ - if (types.fromType == typeof(string)) - { - types = (types.toType, types.fromType); - } - - var converter = TypeDescriptor.GetConverter(types.fromType); - return converter.CanConvertTo(types.toType) ? converter : null; - }, RxApp.SmallCacheLimit); - - /// - public int GetAffinityForObjects(Type fromType, Type toType) - { - var converter = _typeConverterCache.Get((fromType, toType)); - return converter is not null ? 10 : 0; - } - - /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result) - { - if (from is null) - { - result = null; - return true; - } - - var fromType = from.GetType(); - var converter = _typeConverterCache.Get((fromType, toType)); - - if (converter is null) - { - throw new ArgumentException($"Can't convert {fromType} to {toType}. To fix this, register a IBindingTypeConverter"); - } - - try - { - // TODO: This should use conversionHint to determine whether this is locale-aware or not - result = (fromType == typeof(string)) ? - converter.ConvertFrom(from) : converter.ConvertTo(from, toType); - - return true; - } - catch (FormatException) - { - result = null; - return false; - } - catch (Exception e) - { - // Errors from ConvertFrom end up here but wrapped in - // outer exception. Add more types here as required. - // IndexOutOfRangeException is given when trying to - // convert empty strings with some/all? converters - if (e.InnerException is IndexOutOfRangeException || - e.InnerException is FormatException) - { - result = null; - return false; - } - - throw new Exception($"Can't convert from {@from.GetType()} to {toType}.", e); - } - } -} diff --git a/src/ReactiveUI/Platforms/net/PlatformRegistrations.cs b/src/ReactiveUI/Platforms/net/PlatformRegistrations.cs index 271c23de44..f9077f5978 100644 --- a/src/ReactiveUI/Platforms/net/PlatformRegistrations.cs +++ b/src/ReactiveUI/Platforms/net/PlatformRegistrations.cs @@ -13,17 +13,11 @@ namespace ReactiveUI; public class PlatformRegistrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Platform registration uses ComponentModelTypeConverter and RxApp which require dynamic code generation")] - [RequiresUnreferencedCode("Platform registration uses ComponentModelTypeConverter and RxApp which may require unreferenced code")] - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Not all paths use reflection")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); - registerFunction(() => new ComponentModelTypeConverter(), typeof(IBindingTypeConverter)); + registrar.RegisterConstant(static () => new ComponentModelFallbackConverter()); if (!ModeDetector.InUnitTestRunner()) { diff --git a/src/ReactiveUI/Platforms/netstandard2.0/PlatformRegistrations.cs b/src/ReactiveUI/Platforms/netstandard2.0/PlatformRegistrations.cs index 62809ef247..5a3cb06f0d 100644 --- a/src/ReactiveUI/Platforms/netstandard2.0/PlatformRegistrations.cs +++ b/src/ReactiveUI/Platforms/netstandard2.0/PlatformRegistrations.cs @@ -12,13 +12,9 @@ namespace ReactiveUI; public class PlatformRegistrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Platform registration uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Platform registration uses RxApp which may require unreferenced code")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); if (!ModeDetector.InUnitTestRunner()) { diff --git a/src/ReactiveUI/Platforms/tizen/PlatformRegistrations.cs b/src/ReactiveUI/Platforms/tizen/PlatformRegistrations.cs index 979a7079b3..ecb19b4cae 100644 --- a/src/ReactiveUI/Platforms/tizen/PlatformRegistrations.cs +++ b/src/ReactiveUI/Platforms/tizen/PlatformRegistrations.cs @@ -12,16 +12,12 @@ namespace ReactiveUI; public class PlatformRegistrations : IWantsToRegisterStuff { /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Platform registration uses ComponentModelTypeConverter and RxApp which require dynamic code generation")] - [RequiresUnreferencedCode("Platform registration uses ComponentModelTypeConverter and RxApp which may require unreferenced code")] -#endif - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); - registerFunction(() => new PlatformOperations(), typeof(IPlatformOperations)); - registerFunction(() => new ComponentModelTypeConverter(), typeof(IBindingTypeConverter)); + registrar.RegisterConstant(static () => new PlatformOperations()); + registrar.RegisterConstant(static () => new ComponentModelFallbackConverter()); if (!ModeDetector.InUnitTestRunner()) { diff --git a/src/ReactiveUI/Platforms/tvos/UIKitCommandBinders.cs b/src/ReactiveUI/Platforms/tvos/UIKitCommandBinders.cs index 54ca8b2172..300aae31a0 100644 --- a/src/ReactiveUI/Platforms/tvos/UIKitCommandBinders.cs +++ b/src/ReactiveUI/Platforms/tvos/UIKitCommandBinders.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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. @@ -10,32 +10,71 @@ namespace ReactiveUI; /// -/// UI Kit command binder platform registrations. +/// UIKit command binder platform registrations. /// -/// -#if NET6_0_OR_GREATER -[RequiresUnreferencedCode("FlexibleCommandBinder uses reflection for property access and type checking which may require unreferenced code.")] -#endif -public class UIKitCommandBinders : FlexibleCommandBinder +/// +/// +/// This binder registers UIKit-specific command bindings using AOT-friendly event subscription +/// where possible (explicit add/remove handler delegates) and avoids string/reflection-based +/// event hookup. +/// +/// +/// Enabled-state synchronization uses a cached for the platform +/// Enabled property and is performed via the shared infrastructure in +/// . +/// +/// +[Preserve(AllMembers = true)] +public sealed class UIKitCommandBinders : FlexibleCommandBinder { + /// + /// Cached Enabled property for (used by ). + /// + private static readonly PropertyInfo UIControlEnabledProperty = + typeof(UIControl).GetRuntimeProperty(nameof(UIControl.Enabled)) + ?? throw new InvalidOperationException("There is no Enabled property on UIControl which is required for binding."); + + /// + /// Cached Enabled property for . + /// + private static readonly PropertyInfo UIBarButtonItemEnabledProperty = + typeof(UIBarButtonItem).GetRuntimeProperty(nameof(UIBarButtonItem.Enabled)) + ?? throw new InvalidOperationException("There is no Enabled property on UIBarButtonItem which is required for binding."); + /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("UIKitCommandBinders uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("UIKitCommandBinders uses methods that may require unreferenced code")] -#endif public UIKitCommandBinders() { - Register(typeof(UIControl), 9, static (cmd, t, cp) => ForTargetAction(cmd, t, cp, typeof(UIControl).GetRuntimeProperty("Enabled") ?? throw new InvalidOperationException("There is no Enabled property on the UIControl which is needed for binding."))); - Register(typeof(UIBarButtonItem), 10, static (cmd, t, cp) => ForEvent(cmd, t, cp, "Clicked", typeof(UIBarButtonItem).GetRuntimeProperty("Enabled") ?? throw new InvalidOperationException("There is no Enabled property on the UIBarButtonItem which is needed for binding."))); + // UIControl uses UIKit target-action rather than .NET events. + Register( + typeof(UIControl), + affinity: 9, + static (cmd, t, cp) => ForTargetAction(cmd, t, cp, UIControlEnabledProperty)); + + // UIBarButtonItem exposes a normal .NET event ("Clicked"). Use explicit add/remove to avoid reflection. + Register( + typeof(UIBarButtonItem), + affinity: 10, + static (cmd, t, cp) => + { + if (t is not UIBarButtonItem item) + { + return Disposable.Empty; + } + + return ForEvent( + command: cmd, + target: item, + commandParameter: cp, + addHandler: h => item.Clicked += h, + removeHandler: h => item.Clicked -= h, + enabledProperty: UIBarButtonItemEnabledProperty); + }); } /// - /// Gets the UIKitCommandBinders instance. + /// Gets the shared instance. /// -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Deliberate")] -#endif public static Lazy Instance { get; } = new(); } diff --git a/src/ReactiveUI/Platforms/tvos/UIKitObservableForProperty.cs b/src/ReactiveUI/Platforms/tvos/UIKitObservableForProperty.cs index 5c62d5cdbb..475d9abb71 100644 --- a/src/ReactiveUI/Platforms/tvos/UIKitObservableForProperty.cs +++ b/src/ReactiveUI/Platforms/tvos/UIKitObservableForProperty.cs @@ -3,46 +3,95 @@ // 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; + using UIKit; namespace ReactiveUI; /// -/// UIKitObservableForProperty is an object that knows how to -/// create notifications for a given type of object. Implement this if you -/// are porting RxUI to a new UI toolkit, or generally want to enable WhenAny -/// for another type of object that can be observed in a unique way. +/// Provides UIKit-specific observable factories used by ReactiveUI to generate change notifications for +/// UIKit controls in WhenAny* and related operators. /// +/// +/// This implementation registers observable factories for common UIKit properties that change via control +/// events or notifications. +/// [Preserve] -public class UIKitObservableForProperty : ObservableForPropertyBase +public sealed class UIKitObservableForProperty : ObservableForPropertyBase { /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("UIKitObservableForProperty uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("UIKitObservableForProperty uses methods that may require unreferenced code")] -#endif public UIKitObservableForProperty() { - Register(typeof(UIControl), "Value", 20, static (s, p) => ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged)); - Register(typeof(UITextField), "Text", 30, static (s, p) => ObservableFromNotification(s, p, UITextField.TextFieldTextDidChangeNotification)); - Register(typeof(UITextView), "Text", 30, static (s, p) => ObservableFromNotification(s, p, UITextView.TextDidChangeNotification)); - Register(typeof(UISegmentedControl), "SelectedSegment", 30, static (s, p) => ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged)); - Register(typeof(UISegmentedControl), "SelectedSegment", 30, static (s, p) => ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged)); + // UIControl "Value" changes via ValueChanged. + Register( + typeof(UIControl), + "Value", + affinity: 20, + static (sender, expression) => ObservableFromUIControlEvent(sender, expression, UIControlEvent.ValueChanged)); + + // UITextField "Text" changes via notification. + Register( + typeof(UITextField), + "Text", + affinity: 30, + static (sender, expression) => ObservableFromNotification(sender, expression, UITextField.TextFieldTextDidChangeNotification)); + + // UITextView "Text" changes via notification. + Register( + typeof(UITextView), + "Text", + affinity: 30, + static (sender, expression) => ObservableFromNotification(sender, expression, UITextView.TextDidChangeNotification)); + + // UISegmentedControl "SelectedSegment" changes via ValueChanged. + Register( + typeof(UISegmentedControl), + "SelectedSegment", + affinity: 30, + static (sender, expression) => ObservableFromUIControlEvent(sender, expression, UIControlEvent.ValueChanged)); - // Warning: This will stomp the Control's delegate - Register(typeof(UITabBar), "SelectedItem", 30, static (s, p) => ObservableFromEvent(s, p, "ItemSelected")); + // Warning: Event-based observation can impact control behavior depending on external delegate usage. + // Prefer explicit add/remove handler overloads (non-reflection) to improve performance and trimming/AOT compatibility. + Register( + typeof(UITabBar), + "SelectedItem", + affinity: 30, + static (sender, expression) => + { + var tabBar = (UITabBar)sender; + return ObservableFromEvent( + tabBar, + expression, + addHandler: h => tabBar.ItemSelected += h, + removeHandler: h => tabBar.ItemSelected -= h); + }); - // Warning: This will stomp the Control's delegate - Register(typeof(UISearchBar), "Text", 30, static (s, p) => ObservableFromEvent(s, p, "TextChanged")); + // Warning: Event-based observation can impact control behavior depending on external delegate usage. + // Prefer explicit add/remove handler overloads (non-reflection) to improve performance and trimming/AOT compatibility. + Register( + typeof(UISearchBar), + "Text", + affinity: 30, + static (sender, expression) => + { + var searchBar = (UISearchBar)sender; + return ObservableFromEvent( + searchBar, + expression, + addHandler: h => searchBar.TextChanged += h, + removeHandler: h => searchBar.TextChanged -= h); + }); } /// - /// Gets the UI Kit ObservableForProperty instance. + /// Gets the shared instance. /// -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Deliberate")] -#endif + /// + /// The instance is created lazily. Consumers typically register it with the service locator once during + /// application initialization. + /// public static Lazy Instance { get; } = new(); } diff --git a/src/ReactiveUI/Platforms/uikit-common/AutoSuspendHelper.cs b/src/ReactiveUI/Platforms/uikit-common/AutoSuspendHelper.cs index 113567880b..408b607b18 100644 --- a/src/ReactiveUI/Platforms/uikit-common/AutoSuspendHelper.cs +++ b/src/ReactiveUI/Platforms/uikit-common/AutoSuspendHelper.cs @@ -3,6 +3,8 @@ // 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.Runtime.CompilerServices; + using Foundation; using UIKit; @@ -12,83 +14,81 @@ namespace ReactiveUI; /// -/// Bridges iOS lifecycle notifications into so applications can persist and +/// Bridges iOS lifecycle notifications into so applications can persist and /// restore state without manually wiring UIKit events. /// +/// The concrete type. /// /// -/// Instantiate inside your and forward the +/// Instantiate inside your and forward the /// FinishedLaunching, OnActivated, and DidEnterBackground events to the helper. The helper updates /// the shared observables and takes care of requesting background time when persisting /// application state. /// /// -/// -/// -/// -/// _autoSuspendHelper?.OnActivated(application); -/// -/// public override void DidEnterBackground(UIApplication application) => -/// _autoSuspendHelper?.DidEnterBackground(application); -/// } -/// ]]> -/// -/// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("AutoSuspendHelper uses RxApp.SuspensionHost and reflection which require dynamic code generation")] -[RequiresUnreferencedCode("AutoSuspendHelper uses RxApp.SuspensionHost and reflection which may require unreferenced code")] -#endif -public class AutoSuspendHelper : IEnableLogger, IDisposable +public class AutoSuspendHelper< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T> : IEnableLogger, IDisposable + where T : UIApplicationDelegate { private readonly Subject _finishedLaunching = new(); private readonly Subject _activated = new(); private readonly Subject _backgrounded = new(); + private readonly Subject _untimelyDeath = new(); + + private readonly UnhandledExceptionEventHandler _unhandledExceptionHandler; private bool _isDisposed; /// - /// Initializes a new instance of the class. + /// Initializes static members of the class. /// - /// The uiappdelegate. - public AutoSuspendHelper(UIApplicationDelegate appDelegate) + /// + /// + /// This validation runs exactly once per closed generic type and avoids repeated reflection and cache/lock overhead. + /// + /// + /// The call uses the Type-based overload of ThrowIfMethodsNotOverloaded and expresses trimming requirements via + /// on , avoiding RequiresUnreferencedCode + /// propagation. + /// + /// + static AutoSuspendHelper() { Reflection.ThrowIfMethodsNotOverloaded( - nameof(AutoSuspendHelper), - appDelegate, - nameof(FinishedLaunching), - nameof(OnActivated), - nameof(DidEnterBackground)); + nameof(AutoSuspendHelper<>), + typeof(T), + nameof(FinishedLaunching), + nameof(OnActivated), + nameof(DidEnterBackground)); + } - RxApp.SuspensionHost.IsLaunchingNew = Observable.Never; - RxApp.SuspensionHost.IsResuming = _finishedLaunching.Select(_ => Unit.Default); - RxApp.SuspensionHost.IsUnpausing = _activated.Select(_ => Unit.Default); + /// + /// Initializes a new instance of the class. + /// + /// The application delegate instance that forwards lifecycle methods. + /// Thrown when is . + public AutoSuspendHelper(T appDelegate) + { + ArgumentExceptionHelper.ThrowIfNull(appDelegate); + + RxSuspension.SuspensionHost.IsLaunchingNew = Observable.Never; + RxSuspension.SuspensionHost.IsResuming = _finishedLaunching.Select(static _ => Unit.Default); + RxSuspension.SuspensionHost.IsUnpausing = _activated.Select(static _ => Unit.Default); - var untimelyDeath = new Subject(); - AppDomain.CurrentDomain.UnhandledException += (o, e) => untimelyDeath.OnNext(Unit.Default); + // Keep a stable delegate instance so we can unsubscribe on Dispose. + _unhandledExceptionHandler = (_, _) => _untimelyDeath.OnNext(Unit.Default); + AppDomain.CurrentDomain.UnhandledException += _unhandledExceptionHandler; - RxApp.SuspensionHost.ShouldInvalidateState = untimelyDeath; + RxSuspension.SuspensionHost.ShouldInvalidateState = _untimelyDeath; - RxApp.SuspensionHost.ShouldPersistState = _backgrounded.SelectMany(app => + RxSuspension.SuspensionHost.ShouldPersistState = _backgrounded.SelectMany(app => { - var taskId = app.BeginBackgroundTask(new NSAction(() => untimelyDeath.OnNext(Unit.Default))); + var taskId = app.BeginBackgroundTask(new NSAction(() => _untimelyDeath.OnNext(Unit.Default))); - // NB: We're being force-killed, signal invalidate instead + // NB: We're being force-killed, signal invalidate instead. if (taskId == UIApplication.BackgroundTaskInvalid) { - untimelyDeath.OnNext(Unit.Default); + _untimelyDeath.OnNext(Unit.Default); return Observable.Empty; } @@ -97,8 +97,8 @@ public AutoSuspendHelper(UIApplicationDelegate appDelegate) } /// - /// Gets the launch options captured from the most recent call to . Keys are converted - /// to strings and values are stringified for convenience when hydrating state. + /// Gets the launch options captured from the most recent call to . + /// Keys are converted to strings and values are stringified for convenience when hydrating state. /// public IDictionary? LaunchOptions { get; private set; } @@ -106,31 +106,63 @@ public AutoSuspendHelper(UIApplicationDelegate appDelegate) /// Notifies the helper that was /// invoked so it can propagate the observable. /// - /// The application. - /// The launch options. + /// The application instance. + /// The launch options dictionary. public void FinishedLaunching(UIApplication application, NSDictionary launchOptions) { - LaunchOptions = launchOptions is not null - ? launchOptions.Keys.ToDictionary(k => k.ToString(), v => launchOptions[v].ToString()) - : []; + ThrowIfDisposed(); + + // Preserve original behavior: if launchOptions is null, expose an empty dictionary. + if (launchOptions is null) + { + LaunchOptions = new Dictionary(0); + } + else + { + // Avoid LINQ allocations; keep behavior broadly equivalent to ToDictionary. + var keys = launchOptions.Keys; + var dict = new Dictionary(keys.Length); + + for (int i = 0; i < keys.Length; i++) + { + var k = keys[i]; + var keyString = k?.ToString() ?? string.Empty; + + var value = launchOptions[k]; + var valueString = value?.ToString() ?? string.Empty; + + // NSDictionary keys are unique by contract. + dict[keyString] = valueString; + } + + LaunchOptions = dict; + } // NB: This is run in-context (i.e. not scheduled), so by the time this - // statement returns, UIWindow should be created already + // statement returns, UIWindow should be created already. _finishedLaunching.OnNext(application); } /// /// Notifies the helper that occurred. /// - /// The application. - public void OnActivated(UIApplication application) => _activated.OnNext(application); + /// The application instance. + public void OnActivated(UIApplication application) + { + ThrowIfDisposed(); + _activated.OnNext(application); + } /// /// Notifies the helper that was raised so that /// persistence can begin. /// - /// The application. - public void DidEnterBackground(UIApplication application) => _backgrounded.OnNext(application); + /// The application instance. + public void DidEnterBackground(UIApplication application) + { + ThrowIfDisposed(); + _backgrounded.OnNext(application); + } /// public void Dispose() @@ -142,7 +174,7 @@ public void Dispose() /// /// Releases managed resources held by the helper. /// - /// If we are going to call Dispose methods on field items. + /// Whether to release managed resources. protected virtual void Dispose(bool isDisposing) { if (_isDisposed) @@ -150,13 +182,29 @@ protected virtual void Dispose(bool isDisposing) return; } - if (isDisposing) + _isDisposed = true; + + if (!isDisposing) { - _activated?.Dispose(); - _backgrounded?.Dispose(); - _finishedLaunching?.Dispose(); + return; } - _isDisposed = true; + // Unsubscribe first to avoid keeping the helper alive. + AppDomain.CurrentDomain.UnhandledException -= _unhandledExceptionHandler; + + // Dispose subjects to release downstream subscriptions deterministically. + _activated.Dispose(); + _backgrounded.Dispose(); + _finishedLaunching.Dispose(); + _untimelyDeath.Dispose(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ThrowIfDisposed() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(AutoSuspendHelper<>)); + } } } diff --git a/src/ReactiveUI/Platforms/uikit-common/CommonReactiveSource.cs b/src/ReactiveUI/Platforms/uikit-common/CommonReactiveSource.cs index a3f763242a..a6b417575b 100644 --- a/src/ReactiveUI/Platforms/uikit-common/CommonReactiveSource.cs +++ b/src/ReactiveUI/Platforms/uikit-common/CommonReactiveSource.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Specialized; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using DynamicData; @@ -15,104 +16,180 @@ namespace ReactiveUI; -#if NET6_0_OR_GREATER -[RequiresDynamicCode("CommonReactiveSource uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("CommonReactiveSource uses methods that may require unreferenced code")] -#endif +/// +/// Provides a common reactive data source implementation for collection-based UI views backed by sectioned data. +/// +/// The source item type. +/// The UI view type. +/// The UI cell type. +/// The section information type. +/// +/// +/// This type monitors the current and the collections inside each section, and translates +/// collection change notifications into batch updates for the underlying UI adapter. +/// +/// +/// Threading: all operations are expected to run on the creating thread (typically the platform main thread). If an +/// operation occurs off-thread, an is thrown. +/// +/// +/// Trimming/AOT: this implementation avoids expression-tree-based reactive helpers (e.g. WhenAnyValue/ObservableForProperty) +/// and instead filters ReactiveObject change streams by property name. +/// +/// internal sealed class CommonReactiveSource : ReactiveObject, IDisposable where TSectionInfo : ISectionInformation { + /// + /// Adapter used to manipulate the UI view (reload, begin/end updates, insert/delete items, etc.). + /// private readonly IUICollViewAdapter _adapter; + + /// + /// Managed thread id captured at construction time; used to validate calls occur on the expected thread. + /// private readonly int _mainThreadId; + + /// + /// Root disposable for subscriptions created by this instance. + /// private readonly CompositeDisposable _mainDisposables; + + /// + /// Holds subscriptions associated with the current value. Replaced when SectionInfo changes. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Handled by the CompositeDisposable")] private readonly SerialDisposable _sectionInfoDisposable; - private readonly IList<(int section, PendingChange pendingChange)> _pendingChanges; + + /// + /// Pending collection changes captured while the UI is not reloading and before a scheduled batch update is applied. + /// + private readonly List<(int section, PendingChange pendingChange)> _pendingChanges; + + /// + /// Indicates whether pending changes are currently being collected for later application. + /// private bool _isCollectingChanges; + + /// + /// Backing store for . + /// private IReadOnlyList _sectionInfo; /// /// Initializes a new instance of the class. /// /// The adapter to use which we want to display information for. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("CommonReactiveSource uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("CommonReactiveSource uses methods that may require unreferenced code")] -#endif + /// Thrown when is . public CommonReactiveSource(IUICollViewAdapter adapter) { + ArgumentExceptionHelper.ThrowIfNull(adapter); + _adapter = adapter; _mainThreadId = Environment.CurrentManagedThreadId; + _mainDisposables = []; _sectionInfoDisposable = new SerialDisposable(); _mainDisposables.Add(_sectionInfoDisposable); + _pendingChanges = []; _sectionInfo = []; + // Avoid ObservableForProperty/WhenAnyValue (expression trees); filter by property name instead. _mainDisposables.Add( - this - .ObservableForProperty( - x => x.SectionInfo, - beforeChange: true, - skipInitial: true) - .Subscribe( - _ => SectionInfoChanging(), - ex => this.Log().Error(ex, "Error occurred whilst SectionInfo changing."))); + Changing! + .Where(static e => e.PropertyName == nameof(SectionInfo)) + .Subscribe( + _ => SectionInfoChanging(), + ex => this.Log().Error(ex, "Error occurred whilst SectionInfo changing."))); _mainDisposables.Add( - this - .WhenAnyValue, IReadOnlyList>(nameof(SectionInfo)) - .Subscribe( - SectionInfoChanged, - ex => this.Log().Error(ex, "Error occurred when SectionInfo changed."))); + Changed! + .Where(static e => e.PropertyName == nameof(SectionInfo)) + .Subscribe( + _ => SectionInfoChanged(SectionInfo), + ex => this.Log().Error(ex, "Error occurred when SectionInfo changed."))); } + /// + /// Gets or sets the current section information. + /// + /// + /// Assigning this property replaces all subscriptions associated with the prior section information. + /// public IReadOnlyList SectionInfo { get => _sectionInfo; set => this.RaiseAndSetIfChanged(ref _sectionInfo, value); } + /// + /// Gets a value indicating whether debug logging is enabled. + /// private bool IsDebugEnabled => this.Log().Level <= LogLevel.Debug; + /// + /// Returns the number of sections. + /// + /// The number of sections. public int NumberOfSections() { - Debug.Assert(Environment.CurrentManagedThreadId == _mainThreadId, "The thread is not the main thread."); + VerifyOnMainThread(); - var count = SectionInfo.Count; + var count = _sectionInfo.Count; this.Log().Debug(CultureInfo.InvariantCulture, "Reporting number of sections = {0}", count); return count; } + /// + /// Returns the number of rows in a section. + /// + /// The section index. + /// The number of rows in the specified section. public int RowsInSection(int section) { - Debug.Assert(Environment.CurrentManagedThreadId == _mainThreadId, "The thread is not the main thread."); + VerifyOnMainThread(); - var list = (IList)SectionInfo[section].Collection!; + var list = (IList)_sectionInfo[section].Collection!; var count = list.Count; + this.Log().Debug(CultureInfo.InvariantCulture, "Reporting rows in section {0} = {1}", section, count); return count; } + /// + /// Returns the item at the specified index path. + /// + /// The index path. + /// The item at the index path, or . public object? ItemAt(NSIndexPath path) { - Debug.Assert(Environment.CurrentManagedThreadId == _mainThreadId, "The thread is not the main thread."); + VerifyOnMainThread(); - var list = (IList)SectionInfo[path.Section].Collection!; + var list = (IList)_sectionInfo[path.Section].Collection!; this.Log().Debug(CultureInfo.InvariantCulture, "Returning item at {0}-{1}", path.Section, path.Row); return list[path.Row]; } + /// + /// Dequeues and configures a cell for the specified index path. + /// + /// The index path. + /// The configured view cell. public TUIViewCell GetCell(NSIndexPath indexPath) { - Debug.Assert(Environment.CurrentManagedThreadId == _mainThreadId, "The thread is not the main thread."); + VerifyOnMainThread(); this.Log().Debug(CultureInfo.InvariantCulture, "Getting cell for index path {0}-{1}", indexPath.Section, indexPath.Row); - var section = SectionInfo[indexPath.Section]; + + var section = _sectionInfo[indexPath.Section]; var vm = ((IList)section.Collection!)[indexPath.Row]; - var cell = _adapter.DequeueReusableCell(section?.CellKeySelector?.Invoke(vm) ?? NSString.Empty, indexPath); + + var key = section?.CellKeySelector?.Invoke(vm) ?? NSString.Empty; + var cell = _adapter.DequeueReusableCell(key, indexPath); if (cell is IViewFor view) { @@ -120,18 +197,34 @@ public TUIViewCell GetCell(NSIndexPath indexPath) view.ViewModel = vm; } - var initializeCellAction = section?.InitializeCellAction ?? (static _ => { }); + var initializeCellAction = section?.InitializeCellAction ?? NoOpInitializeCell; initializeCellAction(cell); return cell; } + /// + /// Disposes subscriptions and managed resources associated with this instance. + /// public void Dispose() { - _mainDisposables?.Dispose(); - _sectionInfoDisposable?.Dispose(); + _mainDisposables.Dispose(); + } + + /// + /// No-op initializer used when a section does not provide an initialization callback. + /// + /// The cell to initialize. + private static void NoOpInitializeCell(TUIViewCell cell) + { } + /// + /// Builds the list of updates for a pending collection change event. + /// + /// The pending change. + /// An enumerable of updates. + /// Thrown when the action is not supported. private static IEnumerable GetUpdatesForEvent(PendingChange pendingChange) => pendingChange.Action switch { @@ -139,6 +232,7 @@ private static IEnumerable GetUpdatesForEvent(PendingChange pendingChang Enumerable .Range(pendingChange.NewStartingIndex, pendingChange.NewItems is null ? 1 : pendingChange.NewItems.Count) .Select(Update.CreateAdd), + NotifyCollectionChangedAction.Remove => Enumerable .Range(pendingChange.OldStartingIndex, pendingChange.OldItems is null ? 1 : pendingChange.OldItems.Count) @@ -146,23 +240,26 @@ private static IEnumerable GetUpdatesForEvent(PendingChange pendingChang // Use OldStartingIndex for each "Update.Index" because the batch update processes and removes items sequentially // opposed to as one Range operation. - // For example if we are removing the items from indexes 1 to 5. - // When item at index 1 is removed item at index 2 is now at index 1 and so on down the line. NotifyCollectionChangedAction.Move => 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)), + Enumerable + .Range(pendingChange.NewStartingIndex, pendingChange.NewItems is null ? 1 : pendingChange.NewItems.Count) + .Select(Update.CreateAdd)), + NotifyCollectionChangedAction.Replace => Enumerable .Range(pendingChange.NewStartingIndex, pendingChange.NewItems is null ? 1 : pendingChange.NewItems.Count) - .SelectMany(x => new[] { Update.CreateDelete(x), Update.CreateAdd(x) }), + .SelectMany(static x => new[] { Update.CreateDelete(x), Update.CreateAdd(x) }), + _ => throw new NotSupportedException("Don't know how to deal with " + pendingChange.Action), }; + /// + /// Called before changes. Disposes subscriptions for the current SectionInfo. + /// private void SectionInfoChanging() { VerifyOnMainThread(); @@ -171,6 +268,10 @@ private void SectionInfoChanging() _sectionInfoDisposable.Disposable = null; } + /// + /// Called when changes. Subscribes to section structure and item changes. + /// + /// The new section info. private void SectionInfoChanged(IReadOnlyList? sectionInfo) { VerifyOnMainThread(); @@ -186,15 +287,19 @@ private void SectionInfoChanged(IReadOnlyList? sectionInfo) } var notifyCollectionChanged = sectionInfo as INotifyCollectionChanged; - if (notifyCollectionChanged is null) { - this.Log().Warn(CultureInfo.InvariantCulture, "[#{0}] SectionInfo {1} does not implement INotifyCollectionChanged - any added or removed sections will not be reflected in the UI.", sectionInfoId, sectionInfo); + this.Log().Warn( + CultureInfo.InvariantCulture, + "[#{0}] SectionInfo {1} does not implement INotifyCollectionChanged - any added or removed sections will not be reflected in the UI.", + sectionInfoId, + sectionInfo); } - var sectionChanged = (notifyCollectionChanged is null ? - Observable.Never : - notifyCollectionChanged.ObserveCollectionChanges().Select(_ => Unit.Default)) + var sectionChanged = + (notifyCollectionChanged is null + ? Observable.Never + : notifyCollectionChanged.ObserveCollectionChanges().Select(static _ => Unit.Default)) .StartWith(Unit.Default); var disposables = new CompositeDisposable @@ -206,10 +311,13 @@ private void SectionInfoChanged(IReadOnlyList? sectionInfo) SubscribeToSectionInfoChanges(sectionInfoId, sectionInfo, sectionChanged, disposables); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SubscribeToSectionInfoChanges uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SubscribeToSectionInfoChanges uses methods that may require unreferenced code")] -#endif + /// + /// Subscribes to changes in the section collection and to changes within each section's item collection. + /// + /// A correlation id for logging. + /// The current section info. + /// An observable indicating that the section set changed. + /// A disposable container for subscriptions. private void SubscribeToSectionInfoChanges(int sectionInfoId, IReadOnlyList sectionInfo, IObservable sectionChanged, CompositeDisposable disposables) { // holds a single disposable representing the monitoring of sectionInfo. @@ -218,199 +326,261 @@ private void SubscribeToSectionInfoChanges(int sectionInfoId, IReadOnlyList - { - VerifyOnMainThread(); - - this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] Calling ReloadData()", sectionInfoId); - _adapter.ReloadData(); - - // holds all the disposables created to monitor stuff inside the section - var sectionDisposables = new CompositeDisposable(); - sectionInfoDisposable.Disposable = sectionDisposables; - - // holds a single disposable for any outstanding request to apply pending changes - var applyPendingChangesDisposable = new SerialDisposable(); - sectionDisposables.Add(applyPendingChangesDisposable); - - var isReloading = _adapter - .IsReloadingData - .DistinctUntilChanged() - .Do(y => this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] IsReloadingData = {1}", sectionInfoId, y)) - .Publish(); - - var anySectionChanged = sectionInfo - .Select((y, index) => y.Collection!.ObserveCollectionChanges().Select(z => new { Section = index, Change = z })) - .Merge() - .Publish(); - - // since reloads are applied asynchronously, it is possible for data to change whilst the reload is occurring - // thus, we need to ensure any such changes result in another reload - sectionDisposables.Add( - isReloading - .Where(y => y) - .Join( - anySectionChanged, - _ => isReloading, - _ => Observable.Empty, - (_, __) => Unit.Default) - .Subscribe(_ => - { - VerifyOnMainThread(); - this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] A section changed whilst a reload is in progress - forcing another reload.", sectionInfoId); - - _adapter.ReloadData(); - _pendingChanges.Clear(); - _isCollectingChanges = false; - })); - - sectionDisposables.Add( - isReloading - .Where(y => !y) - .Join( - anySectionChanged, - _ => isReloading, - _ => Observable.Empty, - (_, changeDetails) => (changeDetails.Change, changeDetails.Section)) - .Subscribe( - y => - { - VerifyOnMainThread(); - - if (IsDebugEnabled) - { - this.Log().Debug( - CultureInfo.InvariantCulture, - "[#{0}] Change detected in section {1} : Action = {2}, OldStartingIndex = {3}, NewStartingIndex = {4}, OldItems.Count = {5}, NewItems.Count = {6}", - sectionInfoId, - y.Section, - y.Change.EventArgs.Action, - y.Change.EventArgs.OldStartingIndex, - y.Change.EventArgs.NewStartingIndex, - y.Change.EventArgs.OldItems is null ? "null" : y.Change.EventArgs.OldItems.Count.ToString(CultureInfo.InvariantCulture), - y.Change.EventArgs.NewItems is null ? "null" : y.Change.EventArgs.NewItems.Count.ToString(CultureInfo.InvariantCulture)); - } - - if (!_isCollectingChanges) - { - this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] A section changed whilst no reload is in progress - instigating collection of updates for later application.", sectionInfoId); - _isCollectingChanges = true; - - // immediately indicate to the view that there are changes underway, even though we don't apply them immediately - // this ensures that if application code itself calls BeginUpdates/EndUpdates on the view before the changes have been applied, those inconsistencies - // between what's in the data source versus what the view believes is in the data source won't trigger any errors because of the outstanding - // BeginUpdates call (calls to BeginUpdates/EndUpdates can be nested) - this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] BeginUpdates", sectionInfoId); - _adapter.BeginUpdates(); - - applyPendingChangesDisposable.Disposable = RxSchedulers.MainThreadScheduler.Schedule( - () => - { - ApplyPendingChanges(sectionInfoId); - this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] EndUpdates", sectionInfoId); - _adapter.EndUpdates(); - _isCollectingChanges = false; - applyPendingChangesDisposable.Disposable = null; - }); - } - - _pendingChanges.Add((y.Section, new PendingChange(y.Change.EventArgs))); - }, - ex => this.Log().Error(CultureInfo.InvariantCulture, "[#{0}] Error while watching section collection: {1}", sectionInfoId, ex))); - - sectionDisposables.Add(isReloading.Connect()); - sectionDisposables.Add(anySectionChanged.Connect()); - })); + sectionChanged.Subscribe( + _ => + { + VerifyOnMainThread(); + + this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] Calling ReloadData()", sectionInfoId); + _adapter.ReloadData(); + + // holds all the disposables created to monitor stuff inside the section + var sectionDisposables = new CompositeDisposable(); + sectionInfoDisposable.Disposable = sectionDisposables; + + // holds a single disposable for any outstanding request to apply pending changes + var applyPendingChangesDisposable = new SerialDisposable(); + sectionDisposables.Add(applyPendingChangesDisposable); + + var isReloading = _adapter + .IsReloadingData + .DistinctUntilChanged() + .Do(y => this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] IsReloadingData = {1}", sectionInfoId, y)) + .Publish(); + + // Merge per-section collection changes into a single stream. + // This remains a "setup path" rather than a hot per-event path. + var anySectionChanged = sectionInfo + .Select((y, index) => y.Collection!.ObserveCollectionChanges().Select(z => new { Section = index, Change = z })) + .Merge() + .Publish(); + + // since reloads are applied asynchronously, it is possible for data to change whilst the reload is occurring + // thus, we need to ensure any such changes result in another reload + sectionDisposables.Add( + isReloading + .Where(static y => y) + .Join( + anySectionChanged, + _ => isReloading, + _ => Observable.Empty, + static (_, __) => Unit.Default) + .Subscribe( + _ => + { + VerifyOnMainThread(); + + this.Log().Debug( + CultureInfo.InvariantCulture, + "[#{0}] A section changed whilst a reload is in progress - forcing another reload.", + sectionInfoId); + + _adapter.ReloadData(); + _pendingChanges.Clear(); + _isCollectingChanges = false; + })); + + sectionDisposables.Add( + isReloading + .Where(static y => !y) + .Join( + anySectionChanged, + _ => isReloading, + _ => Observable.Empty, + static (_, changeDetails) => (changeDetails.Change, changeDetails.Section)) + .Subscribe( + y => + { + VerifyOnMainThread(); + + if (IsDebugEnabled) + { + var ea = y.Change.EventArgs; + this.Log().Debug( + CultureInfo.InvariantCulture, + "[#{0}] Change detected in section {1} : Action = {2}, OldStartingIndex = {3}, NewStartingIndex = {4}, OldItems.Count = {5}, NewItems.Count = {6}", + sectionInfoId, + y.Section, + ea.Action, + ea.OldStartingIndex, + ea.NewStartingIndex, + ea.OldItems is null ? "null" : ea.OldItems.Count.ToString(CultureInfo.InvariantCulture), + ea.NewItems is null ? "null" : ea.NewItems.Count.ToString(CultureInfo.InvariantCulture)); + } + + if (!_isCollectingChanges) + { + this.Log().Debug( + CultureInfo.InvariantCulture, + "[#{0}] A section changed whilst no reload is in progress - instigating collection of updates for later application.", + sectionInfoId); + + _isCollectingChanges = true; + + // immediately indicate to the view that there are changes underway, even though we don't apply them immediately + this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] BeginUpdates", sectionInfoId); + _adapter.BeginUpdates(); + + applyPendingChangesDisposable.Disposable = + RxSchedulers.MainThreadScheduler.Schedule( + () => + { + ApplyPendingChanges(sectionInfoId); + + this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] EndUpdates", sectionInfoId); + _adapter.EndUpdates(); + + _isCollectingChanges = false; + applyPendingChangesDisposable.Disposable = null; + }); + } + + _pendingChanges.Add((y.Section, new PendingChange(y.Change.EventArgs))); + }, + ex => this.Log().Error(CultureInfo.InvariantCulture, "[#{0}] Error while watching section collection: {1}", sectionInfoId, ex))); + + sectionDisposables.Add(isReloading.Connect()); + sectionDisposables.Add(anySectionChanged.Connect()); + })); } + /// + /// Applies pending changes collected during the current update window as adapter batch operations. + /// + /// A correlation id for logging. + /// Thrown when called off the creating thread. private void ApplyPendingChanges(int sectionInfoId) { - Debug.Assert(Environment.CurrentManagedThreadId == _mainThreadId, "The thread is not the main thread."); + VerifyOnMainThread(); Debug.Assert(_isCollectingChanges, "Currently there are no changes to collect"); + this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] Applying pending changes", sectionInfoId); try { _adapter.PerformUpdates( - () => - { - if (IsDebugEnabled) - { - this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] The pending changes (in order received) are:", sectionInfoId); - - foreach (var (section, pendingChange) in _pendingChanges) - { - this.Log().Debug( - CultureInfo.InvariantCulture, - "[#{0}] Section {1}: Action = {2}, OldStartingIndex = {3}, NewStartingIndex = {4}, OldItems.Count = {5}, NewItems.Count = {6}", - sectionInfoId, - section, - pendingChange.Action, - pendingChange.OldStartingIndex, - pendingChange.NewStartingIndex, - pendingChange.OldItems is null ? "null" : pendingChange.OldItems.Count.ToString(CultureInfo.InvariantCulture), - pendingChange.NewItems is null ? "null" : pendingChange.NewItems.Count.ToString(CultureInfo.InvariantCulture)); - } - } - - foreach (var sectionedUpdates in _pendingChanges.GroupBy(x => x.section)) - { - var section = sectionedUpdates.First().section; - - this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] Processing updates for section {1}", sectionInfoId, section); - - var allSectionChanges = sectionedUpdates - .Select(x => x.pendingChange) - .ToList(); - - if (allSectionChanges.Any(x => x.Action == NotifyCollectionChangedAction.Reset)) - { - this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] Section {1} included a reset notification, so reloading that section.", sectionInfoId, section); - _adapter.ReloadSections(new NSIndexSet((nuint)section)); - continue; - } - - var updates = allSectionChanges - .SelectMany(GetUpdatesForEvent) - .ToList(); - var normalizedUpdates = IndexNormalizer.Normalize(updates); - - if (IsDebugEnabled) - { - this.Log().Debug( - CultureInfo.InvariantCulture, - "[#{0}] Updates for section {1}: {2}", - sectionInfoId, - section, - string.Join(":", updates)); - - this.Log().Debug( - CultureInfo.InvariantCulture, - "[#{0}] Normalized updates for section {1}: {2}", - sectionInfoId, - section, - string.Join(":", normalizedUpdates)); - } - - foreach (var normalizedUpdate in normalizedUpdates) - { - switch (normalizedUpdate?.Type) - { - case UpdateType.Add: - DoUpdate(_adapter.InsertItems, [normalizedUpdate.Index], section); - break; - - case UpdateType.Delete: - DoUpdate(_adapter.DeleteItems, [normalizedUpdate.Index], section); - break; - - default: - throw new NotSupportedException(); - } - } - } - }, - () => this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] Pending changes applied", sectionInfoId)); + () => + { + if (_pendingChanges.Count == 0) + { + return; + } + + if (IsDebugEnabled) + { + this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] The pending changes (in order received) are:", sectionInfoId); + + for (var i = 0; i < _pendingChanges.Count; i++) + { + var (section, pendingChange) = _pendingChanges[i]; + this.Log().Debug( + CultureInfo.InvariantCulture, + "[#{0}] Section {1}: Action = {2}, OldStartingIndex = {3}, NewStartingIndex = {4}, OldItems.Count = {5}, NewItems.Count = {6}", + sectionInfoId, + section, + pendingChange.Action, + pendingChange.OldStartingIndex, + pendingChange.NewStartingIndex, + pendingChange.OldItems is null ? "null" : pendingChange.OldItems.Count.ToString(CultureInfo.InvariantCulture), + pendingChange.NewItems is null ? "null" : pendingChange.NewItems.Count.ToString(CultureInfo.InvariantCulture)); + } + } + + // Sort by section to process per section without GroupBy allocations. + _pendingChanges.Sort(static (a, b) => a.section.CompareTo(b.section)); + + var iChange = 0; + while (iChange < _pendingChanges.Count) + { + var section = _pendingChanges[iChange].section; + + this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] Processing updates for section {1}", sectionInfoId, section); + + // Scan to determine the range [iChange, iEnd) for this section and whether any Reset is present. + var iEnd = iChange; + var hasReset = false; + + while (iEnd < _pendingChanges.Count && _pendingChanges[iEnd].section == section) + { + if (_pendingChanges[iEnd].pendingChange.Action == NotifyCollectionChangedAction.Reset) + { + hasReset = true; + } + + iEnd++; + } + + if (hasReset) + { + this.Log().Debug( + CultureInfo.InvariantCulture, + "[#{0}] Section {1} included a reset notification, so reloading that section.", + sectionInfoId, + section); + + _adapter.ReloadSections(new NSIndexSet((nuint)section)); + iChange = iEnd; + continue; + } + + // Materialize updates for this section. + // We keep using the existing normalization routine; updates list is per-section and bounded. + var updates = new List(); + + for (var j = iChange; j < iEnd; j++) + { + foreach (var update in GetUpdatesForEvent(_pendingChanges[j].pendingChange)) + { + if (update is null) + { + continue; + } + + updates.Add(update); + } + } + + var normalizedUpdates = IndexNormalizer.Normalize(updates); + + if (IsDebugEnabled) + { + this.Log().Debug( + CultureInfo.InvariantCulture, + "[#{0}] Updates for section {1}: {2}", + sectionInfoId, + section, + string.Join(":", updates)); + + this.Log().Debug( + CultureInfo.InvariantCulture, + "[#{0}] Normalized updates for section {1}: {2}", + sectionInfoId, + section, + string.Join(":", normalizedUpdates)); + } + + for (var k = 0; k < normalizedUpdates.Count; k++) + { + var normalizedUpdate = normalizedUpdates[k]; + switch (normalizedUpdate?.Type) + { + case UpdateType.Add: + DoUpdate(_adapter.InsertItems, normalizedUpdate.Index, section); + break; + + case UpdateType.Delete: + DoUpdate(_adapter.DeleteItems, normalizedUpdate.Index, section); + break; + + default: + throw new NotSupportedException(); + } + } + + iChange = iEnd; + } + }, + () => this.Log().Debug(CultureInfo.InvariantCulture, "[#{0}] Pending changes applied", sectionInfoId)); } finally { @@ -418,44 +588,116 @@ private void ApplyPendingChanges(int sectionInfoId) } } - private void DoUpdate(Action method, IEnumerable update, int section) + /// + /// Applies an adapter update method for a single index within a section. + /// + /// The adapter method. + /// The row index. + /// The section index. + private void DoUpdate(Action method, int index, int section) { - var toChange = update - .Select(x => NSIndexPath.FromRowSection(x, section)) - .ToArray(); + // Single item -> avoid IEnumerable allocations and ToArray. + var toChange = new[] { NSIndexPath.FromRowSection(index, section) }; if (IsDebugEnabled) { this.Log().Debug( - CultureInfo.InvariantCulture, - "Calling {0}: [{1}]", - method.Method.Name, - string.Join(",", toChange.Select(x => x.Section + "-" + x.Row))); + CultureInfo.InvariantCulture, + "Calling {0}: [{1}]", + method.Method.Name, + toChange[0].Section + "-" + toChange[0].Row); } method(toChange); } + /// + /// Throws if the current thread is not the creating thread. + /// + /// Thrown when called off the creating thread. private void VerifyOnMainThread() { if (Environment.CurrentManagedThreadId != _mainThreadId) { - throw new InvalidOperationException("An operation has occurred off the main thread that must be performed on it. Be sure to schedule changes to the underlying data correctly."); + throw new InvalidOperationException( + "An operation has occurred off the main thread that must be performed on it. Be sure to schedule changes to the underlying data correctly."); } } - // rather than storing NotifyCollectionChangeEventArgs instances, we store instances of this class instead - // storing NotifyCollectionChangeEventArgs doesn't always work because external code can mutate the instance before we get a chance to apply it - private sealed class PendingChange(NotifyCollectionChangedEventArgs ea) + /// + /// Snapshot of a collection change event that is resilient to external mutation of the original event args. + /// + /// + /// Rather than storing instances, we store instances of this type. + /// Storing the event args directly does not always work because external code can mutate the instance before it + /// can be applied. + /// + private sealed class PendingChange { - public NotifyCollectionChangedAction Action { get; } = ea.Action; - - public IList? OldItems { get; } = ea.OldItems?.Cast().ToList(); - - public IList? NewItems { get; } = ea.NewItems?.Cast().ToList(); + /// + /// Initializes a new instance of the class. + /// + /// The original collection change event arguments. + /// Thrown when is . + public PendingChange(NotifyCollectionChangedEventArgs ea) + { + ArgumentExceptionHelper.ThrowIfNull(ea); - public int OldStartingIndex { get; } = ea.OldStartingIndex; + Action = ea.Action; + OldItems = CopyItems(ea.OldItems); + NewItems = CopyItems(ea.NewItems); + OldStartingIndex = ea.OldStartingIndex; + NewStartingIndex = ea.NewStartingIndex; + } - public int NewStartingIndex { get; } = ea.NewStartingIndex; + /// + /// Gets the collection change action. + /// + public NotifyCollectionChangedAction Action { get; } + + /// + /// Gets the copied old items. + /// + public IList? OldItems { get; } + + /// + /// Gets the copied new items. + /// + public IList? NewItems { get; } + + /// + /// Gets the old starting index. + /// + public int OldStartingIndex { get; } + + /// + /// Gets the new starting index. + /// + public int NewStartingIndex { get; } + + /// + /// Creates a shallow copy of the items in the specified list, returning a new list containing the same + /// elements. + /// + /// The copy is shallow; reference types within the list are not cloned. The returned + /// list is always of type . + /// The list whose items are to be copied. Can be null or empty. + /// A new list containing the elements of . Returns null if + /// is null, or an empty list if is empty. + private static IList? CopyItems(IList? source) + { + if (source is null || source.Count == 0) + { + return source is null ? null : Array.Empty(); + } + + var list = new List(source.Count); + for (var i = 0; i < source.Count; i++) + { + list.Add(source[i]!); + } + + return list; + } } } diff --git a/src/ReactiveUI/Platforms/uikit-common/FlexibleCommandBinder.cs b/src/ReactiveUI/Platforms/uikit-common/FlexibleCommandBinder.cs index 4165f20903..e5fd4e884b 100644 --- a/src/ReactiveUI/Platforms/uikit-common/FlexibleCommandBinder.cs +++ b/src/ReactiveUI/Platforms/uikit-common/FlexibleCommandBinder.cs @@ -6,222 +6,556 @@ using System.Reflection; using System.Windows.Input; -using ReactiveUI.Helpers; - using UIKit; namespace ReactiveUI; /// -/// Generic command binder platform registrations. +/// Base class for platform command binders that register per-type binding factories with an affinity score. /// -/// -#if NET6_0_OR_GREATER -[RequiresDynamicCode("FlexibleCommandBinder uses reflection for property access and type checking which require dynamic code generation")] -[RequiresUnreferencedCode("FlexibleCommandBinder uses reflection for property access and type checking which may require unreferenced code")] -#endif +/// +/// +/// This type is intended for platform implementations (Android, iOS, etc.) that need to bind an +/// to UI controls with platform-specific semantics. +/// +/// +/// Threading: registrations are mutable; lookups are served from a versioned snapshot to avoid locking on +/// the common path. Binding factories are invoked outside locks. +/// +/// +/// Trimming/AOT: the default binding selection method accepts an unknown runtime target type and may call +/// reflection-based helpers (e.g., ). Reflection-based methods are annotated with +/// and where applicable. +/// Prefer the add/remove handler overload for AOT-safe event binding. +/// +/// public abstract class FlexibleCommandBinder : ICreatesCommandBinding { /// - /// Configuration map. + /// A single synchronization gate for all mutable state in this instance. + /// + private readonly object _gate = new(); + + /// + /// Mutable registration map; only accessed under . + /// + private readonly Dictionary _config = []; + + /// + /// A version counter incremented on each registration mutation. + /// + private int _version; + + /// + /// A snapshot of registrations used for lock-free reads. /// - private readonly Dictionary _config = - []; + private Entry[]? _snapshot; + + /// + /// A snapshot version that corresponds to . + /// + private int _snapshotVersion; /// - public int GetAffinityForObject(Type type, bool hasEventTarget) + public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>(bool hasEventTarget) { if (hasEventTarget) { return 0; } - var match = _config.Keys - .Where(x => x.IsAssignableFrom(type)) - .OrderByDescending(x => _config[x].Affinity) - .FirstOrDefault(); + var entries = GetSnapshot(); + var targetType = typeof(T); + + var bestAffinity = 0; - if (match is null) + // Scan all assignable registrations; choose highest affinity. + for (var i = 0; i < entries.Length; i++) { - return 0; + var entry = entries[i]; + if (!entry.Type.IsAssignableFrom(targetType)) + { + continue; + } + + var affinity = entry.Affinity; + if (affinity > bestAffinity) + { + bestAffinity = affinity; + } } - var typeProperties = _config[match]; - return typeProperties.Affinity; + return bestAffinity; } /// -#if NET6_0_OR_GREATER - public int GetAffinityForObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>( - bool hasEventTarget) -#else - public int GetAffinityForObject( - bool hasEventTarget) -#endif + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T>( + ICommand? command, + T? target, + IObservable commandParameter) + where T : class { - if (hasEventTarget) + ArgumentExceptionHelper.ThrowIfNull(target); + + var entries = GetSnapshot(); + var runtimeType = target.GetType(); + + Entry? best = null; + var bestAffinity = int.MinValue; + + for (var i = 0; i < entries.Length; i++) { - return 0; - } + var entry = entries[i]; + if (!entry.Type.IsAssignableFrom(runtimeType)) + { + continue; + } - var match = _config.Keys - .Where(x => x.IsAssignableFrom(typeof(T))) - .OrderByDescending(x => _config[x].Affinity) - .FirstOrDefault(); + if (entry.Affinity > bestAffinity) + { + bestAffinity = entry.Affinity; + best = entry; + } + } - if (match is null) + if (best is null || best.Value.Factory is null) { - return 0; + throw new NotSupportedException($"CommandBinding for {runtimeType.Name} is not supported"); } - var typeProperties = _config[match]; - return typeProperties.Affinity; + // Never invoke user code under locks; snapshot factories are safe to call directly here. + return best.Value.Factory(command, target, commandParameter) ?? Disposable.Empty; } /// - public IDisposable? BindCommandToObject(ICommand? command, object? target, IObservable commandParameter) + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public virtual IDisposable? BindCommandToObject( + ICommand? command, + T? target, + IObservable commandParameter, + string eventName) + where T : class => + Disposable.Empty; + + /// + public virtual IDisposable? BindCommandToObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>( + ICommand? command, + T? target, + IObservable commandParameter, + Action> addHandler, + Action> removeHandler) + where T : class + where TEventArgs : EventArgs { ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(addHandler); + ArgumentNullException.ThrowIfNull(removeHandler); - var type = target.GetType(); + // Match existing binder behavior: null command means "no binding". + if (command is null) + { + return Disposable.Empty; + } + + commandParameter ??= Observable.Return((object?)target); + + object? latestParam = null; + + void Handler(object? sender, TEventArgs e) + { + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } - 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"); - var typeProperties = _config[match]; + var paramSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); + addHandler(Handler); - return typeProperties?.CreateBinding?.Invoke(command, target, commandParameter) ?? Disposable.Empty; + return new CompositeDisposable( + paramSub, + Disposable.Create(() => removeHandler(Handler))); } - /// - public IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter, string eventName) - where TEventArgs : EventArgs => throw new NotImplementedException(); + /// + /// Creates a command binding from an explicit event subscription API and an enabled property. + /// + /// The target type that exposes the event. + /// The event args type. + /// The command to execute when the event fires. + /// The target object that exposes the event. + /// An observable providing the latest command parameter. + /// Adds the event handler to the target. + /// Removes the event handler from the target. + /// A property used to set enabled state (best-effort). + /// A disposable that unsubscribes the event and stops updating enabled state. + /// + /// Thrown when , , , + /// , or is . + /// + /// + /// This overload is AOT-compatible: it does not use reflection-based event subscription. + /// Enabled state synchronization still depends on the provided . + /// + protected static IDisposable ForEvent( + ICommand? command, + TTarget target, + IObservable commandParameter, + Action> addHandler, + Action> removeHandler, + PropertyInfo enabledProperty) + where TTarget : class + where TEventArgs : EventArgs + { + ArgumentExceptionHelper.ThrowIfNull(command); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(addHandler); + ArgumentNullException.ThrowIfNull(removeHandler); + ArgumentExceptionHelper.ThrowIfNull(enabledProperty); + + commandParameter ??= Observable.Return((object?)target); + + object? latestParam = null; + + void Handler(object? sender, TEventArgs e) + { + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } + + // Subscribe parameter first so we have best effort latest value before the first event. + var paramSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); + + addHandler(Handler); + var eventDisp = Disposable.Create(() => removeHandler(Handler)); + + var enabledSetter = Reflection.GetValueSetterForProperty(enabledProperty); + if (enabledSetter is null) + { + return new CompositeDisposable(paramSub, eventDisp); + } + + // Initial enabled state. + enabledSetter(target, command.CanExecute(Volatile.Read(ref latestParam)), null); + + var canExecuteSub = Observable.FromEvent( + eventHandler => + { + void CanExecuteHandler(object? s, EventArgs e) => + eventHandler(command.CanExecute(Volatile.Read(ref latestParam))); + return CanExecuteHandler; + }, + h => command.CanExecuteChanged += h, + h => command.CanExecuteChanged -= h) + .Subscribe(x => enabledSetter(target, x, null)); + + return new CompositeDisposable(paramSub, eventDisp, canExecuteSub); + } /// - /// Creates a commands binding from event and a property. + /// Creates a command binding from an explicit event subscription API and an enabled property. /// - /// The binding from event. - /// Command. - /// Target. - /// Command parameter. - /// Event name. - /// Enabled Property. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ForEvent uses Reflection.GetValueSetterForProperty which requires dynamic code generation")] - [RequiresUnreferencedCode("ForEvent uses Reflection.GetValueSetterForProperty which may require unreferenced code")] -#endif - protected static IDisposable ForEvent(ICommand? command, object? target, IObservable commandParameter, string eventName, PropertyInfo enabledProperty) + /// The target type that exposes the event. + /// The command to execute when the event fires. + /// The target object that exposes the event. + /// An observable providing the latest command parameter. + /// Adds the event handler to the target. + /// Removes the event handler from the target. + /// A property used to set enabled state (best-effort). + /// A disposable that unsubscribes the event and stops updating enabled state. + /// + /// Thrown when , , , + /// , or is . + /// + /// + /// This overload is AOT-compatible: it does not use reflection-based event subscription. + /// Enabled state synchronization still depends on the provided . + /// + protected static IDisposable ForEvent( + ICommand? command, + TTarget target, + IObservable commandParameter, + Action addHandler, + Action removeHandler, + PropertyInfo enabledProperty) + where TTarget : class { ArgumentExceptionHelper.ThrowIfNull(command); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(addHandler); + ArgumentNullException.ThrowIfNull(removeHandler); + ArgumentExceptionHelper.ThrowIfNull(enabledProperty); - commandParameter ??= Observable.Return(target); + commandParameter ??= Observable.Return((object?)target); object? latestParam = null; - var ctl = target!; - var actionDisp = Observable.FromEventPattern(ctl, eventName).Subscribe((_) => + void Handler(object? sender, EventArgs e) { - if (command.CanExecute(latestParam)) + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) { - command.Execute(latestParam); + command.Execute(param); } - }); + } + + // Subscribe parameter first so we have best effort latest value before the first event. + var paramSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); + + addHandler(Handler); + var eventDisp = Disposable.Create(() => removeHandler(Handler)); var enabledSetter = Reflection.GetValueSetterForProperty(enabledProperty); if (enabledSetter is null) { - return actionDisp; + return new CompositeDisposable(paramSub, eventDisp); } - // initial enabled state - enabledSetter(target, command.CanExecute(latestParam), null); + // Initial enabled state. + enabledSetter(target, command.CanExecute(Volatile.Read(ref latestParam)), null); - return new CompositeDisposable( - actionDisp, - commandParameter.Subscribe(x => latestParam = x), - Observable.FromEvent( - eventHandler => - { - void Handler(object? sender, EventArgs e) => eventHandler(command.CanExecute(latestParam)); - return Handler; - }, - x => command.CanExecuteChanged += x, - x => command.CanExecuteChanged -= x) - .Subscribe(x => enabledSetter(target, x, null))); + var canExecuteSub = Observable.FromEvent( + eventHandler => + { + void CanExecuteHandler(object? s, EventArgs e) => + eventHandler(command.CanExecute(Volatile.Read(ref latestParam))); + return CanExecuteHandler; + }, + h => command.CanExecuteChanged += h, + h => command.CanExecuteChanged -= h) + .Subscribe(x => enabledSetter(target, x, null)); + + return new CompositeDisposable(paramSub, eventDisp, canExecuteSub); } /// - /// Creates a commands binding from event and a property. + /// Creates a command binding from a named event and an enabled property. /// - /// The command. - /// The target object. - /// The command parameter. - /// The enabled property. - /// Returns a disposable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ForTargetAction uses Reflection.GetValueSetterForProperty which requires dynamic code generation")] - [RequiresUnreferencedCode("ForTargetAction uses Reflection.GetValueSetterForProperty which may require unreferenced code")] -#endif - protected static IDisposable ForTargetAction(ICommand? command, object? target, IObservable commandParameter, PropertyInfo enabledProperty) + /// The command to execute when the event fires. + /// The UI target object that exposes the event. + /// An observable providing the latest command parameter. + /// The event name to subscribe to. + /// A property to set enabled state (best-effort). + /// A disposable that unsubscribes the event and stops updating enabled state. + /// Thrown when is null. + /// + /// This helper uses reflection-based event subscription and is not trimming-safe. + /// + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + [RequiresDynamicCode("String/reflection-based event binding uses reflection and may require dynamic code generation.")] + protected static IDisposable ForEvent( + ICommand? command, + object? target, + IObservable commandParameter, + string eventName, + PropertyInfo enabledProperty) { ArgumentExceptionHelper.ThrowIfNull(command); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(eventName); + ArgumentExceptionHelper.ThrowIfNull(enabledProperty); commandParameter ??= Observable.Return(target); object? latestParam = null; - var actionDisposable = Disposable.Empty; + var paramSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); - if (target is UIControl ctl) + var actionSub = Observable.FromEventPattern(target, eventName).Subscribe(_ => { - var eh = new EventHandler((o, e) => + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) { - if (command.CanExecute(latestParam)) + command.Execute(param); + } + }); + + var enabledSetter = Reflection.GetValueSetterForProperty(enabledProperty); + if (enabledSetter is null) + { + return new CompositeDisposable(paramSub, actionSub); + } + + enabledSetter(target, command.CanExecute(Volatile.Read(ref latestParam)), null); + + var canExecuteSub = Observable.FromEvent( + eventHandler => { - command.Execute(latestParam); - } - }); + void Handler(object? sender, EventArgs e) => + eventHandler(command.CanExecute(Volatile.Read(ref latestParam))); + return Handler; + }, + h => command.CanExecuteChanged += h, + h => command.CanExecuteChanged -= h) + .Subscribe(x => enabledSetter(target, x, null)); + + return new CompositeDisposable(paramSub, actionSub, canExecuteSub); + } + + /// + /// Creates a command binding for UIKit controls using + /// and an enabled property. + /// + /// The command to execute when the control is activated. + /// The target object, expected to be a . + /// An observable providing the latest command parameter. + /// The property used to set enabled state. + /// A disposable that unbinds the handler and stops updating enabled state. + /// Thrown when is null. + protected static IDisposable ForTargetAction( + ICommand? command, + object? target, + IObservable commandParameter, + PropertyInfo enabledProperty) + { + ArgumentExceptionHelper.ThrowIfNull(command); + ArgumentExceptionHelper.ThrowIfNull(target); + ArgumentExceptionHelper.ThrowIfNull(enabledProperty); + + commandParameter ??= Observable.Return(target); - ctl.AddTarget(eh, UIControlEvent.TouchUpInside); - actionDisposable = Disposable.Create(() => ctl.RemoveTarget(eh, UIControlEvent.TouchUpInside)); + if (target is not UIControl ctl) + { + return Disposable.Empty; } + object? latestParam = null; + + // Stable handler instance for deterministic unsubscribe. + void Handler(object? sender, EventArgs e) + { + var param = Volatile.Read(ref latestParam); + if (command.CanExecute(param)) + { + command.Execute(param); + } + } + + var paramSub = commandParameter.Subscribe(x => Volatile.Write(ref latestParam, x)); + + // UIKit target-action via EventHandler is supported through UIControl's AddTarget overload. + ctl.AddTarget(Handler, UIControlEvent.TouchUpInside); + var actionDisp = Disposable.Create(() => ctl.RemoveTarget(Handler, UIControlEvent.TouchUpInside)); + var enabledSetter = Reflection.GetValueSetterForProperty(enabledProperty); if (enabledSetter is null) { - return actionDisposable; + return new CompositeDisposable(paramSub, actionDisp); } - // Initial enabled state - enabledSetter(target, command.CanExecute(latestParam), null); + enabledSetter(target, command.CanExecute(Volatile.Read(ref latestParam)), null); - return new CompositeDisposable( - actionDisposable, - commandParameter.Subscribe(x => latestParam = x), - Observable.FromEvent( - eventHandler => - { - void Handler(object? sender, EventArgs e) => eventHandler(command.CanExecute(latestParam)); - return Handler; - }, - x => command.CanExecuteChanged += x, - x => command.CanExecuteChanged -= x) - .Subscribe(x => enabledSetter(target, x, null))); + var canExecuteSub = Observable.FromEvent( + eventHandler => + { + void CanExecuteHandler(object? s, EventArgs e) => + eventHandler(command.CanExecute(Volatile.Read(ref latestParam))); + return CanExecuteHandler; + }, + h => command.CanExecuteChanged += h, + h => command.CanExecuteChanged -= h) + .Subscribe(x => enabledSetter(target, x, null)); + + return new CompositeDisposable(paramSub, actionDisp, canExecuteSub); } /// - /// Registers an observable factory for the specified type and property. + /// Registers a binding factory for a type with an affinity score. /// - /// The type. - /// The affinity. - /// The create binding. - protected void Register(Type type, int affinity, Func, IDisposable> createBinding) => _config[type] = new CommandBindingInfo { Affinity = affinity, CreateBinding = createBinding }; + /// The registered type. + /// The affinity score used to select among candidates. + /// The factory that creates the binding. + /// Thrown when or is null. + protected void Register(Type type, int affinity, Func, IDisposable> createBinding) + { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(createBinding); - private class CommandBindingInfo + lock (_gate) + { + _config[type] = new CommandBindingInfo(affinity, createBinding); + _version++; + _snapshot = null; + } + } + + /// + /// Produces or returns a cached snapshot of registrations for lock-free reads. + /// + /// The current snapshot. + private Entry[] GetSnapshot() { - public int Affinity { get; set; } + var snapshot = Volatile.Read(ref _snapshot); + var snapshotVersion = Volatile.Read(ref _snapshotVersion); + + if (snapshot is not null && snapshotVersion == Volatile.Read(ref _version)) + { + return snapshot; + } + + lock (_gate) + { + // Recheck under lock. + var v = _version; + snapshot = _snapshot; + if (snapshot is not null && _snapshotVersion == v) + { + return snapshot; + } + + var entries = new Entry[_config.Count]; + var i = 0; + + foreach (var kvp in _config) + { + var info = kvp.Value; + entries[i++] = new Entry(kvp.Key, info.Affinity, info.CreateBinding); + } + + Volatile.Write(ref _snapshotVersion, v); + Volatile.Write(ref _snapshot, entries); + + return entries; + } + } + + /// + /// Immutable snapshot entry for a registered binding factory. + /// + private readonly record struct Entry( + Type Type, + int Affinity, + Func, IDisposable>? Factory); + + /// + /// Stores binding configuration for a registered type. + /// + private sealed class CommandBindingInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The affinity score. + /// The binding factory. + public CommandBindingInfo(int affinity, Func, IDisposable> createBinding) + { + Affinity = affinity; + CreateBinding = createBinding; + } + + /// + /// Gets the affinity score for this binding. + /// + public int Affinity { get; } - public Func, IDisposable>? CreateBinding { get; set; } + /// + /// Gets the binding factory. + /// + public Func, IDisposable> CreateBinding { get; } } } diff --git a/src/ReactiveUI/Platforms/uikit-common/IUICollViewAdapter.cs b/src/ReactiveUI/Platforms/uikit-common/IUICollViewAdapter.cs index d65e33a070..62dca7a05c 100644 --- a/src/ReactiveUI/Platforms/uikit-common/IUICollViewAdapter.cs +++ b/src/ReactiveUI/Platforms/uikit-common/IUICollViewAdapter.cs @@ -23,10 +23,6 @@ internal interface IUICollViewAdapter /// /// Reloads the data. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReloadData uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReloadData uses methods that may require unreferenced code")] -#endif void ReloadData(); /// diff --git a/src/ReactiveUI/Platforms/uikit-common/PlatformRegistrations.cs b/src/ReactiveUI/Platforms/uikit-common/PlatformRegistrations.cs index c3b66df45c..d4817d19ad 100644 --- a/src/ReactiveUI/Platforms/uikit-common/PlatformRegistrations.cs +++ b/src/ReactiveUI/Platforms/uikit-common/PlatformRegistrations.cs @@ -13,18 +13,28 @@ namespace ReactiveUI; public class PlatformRegistrations : IWantsToRegisterStuff { /// - [RequiresUnreferencedCode("Uses reflection to create instances of types.")] - [RequiresDynamicCode("Uses reflection to create instances of types.")] - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); + ArgumentExceptionHelper.ThrowIfNull(registrar); - registerFunction(static () => new PlatformOperations(), typeof(IPlatformOperations)); - registerFunction(static () => new ComponentModelTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new UIKitObservableForProperty(), typeof(ICreatesObservableForProperty)); - registerFunction(static () => new UIKitCommandBinders(), typeof(ICreatesCommandBinding)); - registerFunction(static () => new DateTimeNSDateConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new KVOObservableForProperty(), typeof(ICreatesObservableForProperty)); + registrar.RegisterConstant(static () => new PlatformOperations()); + registrar.RegisterConstant(static () => new ComponentModelFallbackConverter()); + registrar.RegisterConstant(static () => new UIKitObservableForProperty()); + registrar.RegisterConstant(static () => new UIKitCommandBinders()); + + // DateTime ↔ NSDate converters + registrar.RegisterConstant(static () => new DateTimeToNSDateConverter()); + registrar.RegisterConstant(static () => new NullableDateTimeToNSDateConverter()); + registrar.RegisterConstant(static () => new NSDateToDateTimeConverter()); + registrar.RegisterConstant(static () => new NSDateToNullableDateTimeConverter()); + + // DateTimeOffset ↔ NSDate converters + registrar.RegisterConstant(static () => new DateTimeOffsetToNSDateConverter()); + registrar.RegisterConstant(static () => new NullableDateTimeOffsetToNSDateConverter()); + registrar.RegisterConstant(static () => new NSDateToDateTimeOffsetConverter()); + registrar.RegisterConstant(static () => new NSDateToNullableDateTimeOffsetConverter()); + + registrar.RegisterConstant(static () => new KVOObservableForProperty()); if (!ModeDetector.InUnitTestRunner()) { @@ -32,6 +42,6 @@ public void Register(Action, Type> registerFunction) RxSchedulers.MainThreadScheduler = new WaitForDispatcherScheduler(static () => new NSRunloopScheduler()); } - registerFunction(static () => new AppSupportJsonSuspensionDriver(), typeof(ISuspensionDriver)); + registrar.RegisterConstant(static () => new AppSupportJsonSuspensionDriver()); } } diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionReusableView.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionReusableView.cs index eb441be6a7..164f9fe810 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionReusableView.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionReusableView.cs @@ -16,10 +16,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCollectionReusableView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCollectionReusableView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveCollectionReusableView : UICollectionReusableView, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { private readonly Subject _activated = new(); @@ -152,10 +148,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCollectionReusableView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCollectionReusableView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveCollectionReusableView : ReactiveCollectionReusableView, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionView.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionView.cs index 2f630131e0..959db7fab0 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionView.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionView.cs @@ -16,10 +16,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCollectionView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCollectionView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveCollectionView : UICollectionView, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate, ICanForceManualActivation { private readonly Subject _activated = new(); @@ -137,10 +133,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCollectionView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCollectionView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveCollectionView : ReactiveCollectionView, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewCell.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewCell.cs index 621c84843c..34e8ab95e1 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewCell.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewCell.cs @@ -16,10 +16,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCollectionViewCell inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCollectionViewCell inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveCollectionViewCell : UICollectionViewCell, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { private readonly Subject _activated = new(); @@ -131,10 +127,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCollectionViewCell inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCollectionViewCell inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveCollectionViewCell : ReactiveCollectionViewCell, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewController.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewController.cs index 8e29d0cc81..22f234c397 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewController.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewController.cs @@ -14,10 +14,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCollectionViewController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCollectionViewController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveCollectionViewController : UICollectionViewController, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { @@ -149,10 +145,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCollectionViewController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCollectionViewController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveCollectionViewController : ReactiveCollectionViewController, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSource.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSource.cs index 545d9b3f14..047535d9c3 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSource.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSource.cs @@ -18,10 +18,6 @@ namespace ReactiveUI; /// View items are animated in and out as items are added. /// /// The source type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveCollectionViewSource uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveCollectionViewSource uses methods that may require unreferenced code")] -#endif public class ReactiveCollectionViewSource : UICollectionViewSource, IReactiveNotifyPropertyChanged>, IHandleObservableErrors, IReactiveObject { private readonly CommonReactiveSource> _commonSource; @@ -34,10 +30,6 @@ public class ReactiveCollectionViewSource : UICollectionViewSource, IRe /// The notify collection changed. /// The cell key. /// The cell initialization action. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveCollectionViewSource uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReactiveCollectionViewSource uses methods that may require unreferenced code")] -#endif public ReactiveCollectionViewSource(UICollectionView collectionView, INotifyCollectionChanged collection, NSString cellKey, Action? initializeCellAction = null) : this(collectionView) => Data = [new CollectionViewSectionInformation(collection, cellKey, initializeCellAction) @@ -47,10 +39,6 @@ public ReactiveCollectionViewSource(UICollectionView collectionView, INotifyColl /// Initializes a new instance of the class. /// /// The ui collection view. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveCollectionViewSource uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReactiveCollectionViewSource uses methods that may require unreferenced code")] -#endif public ReactiveCollectionViewSource(UICollectionView collectionView) { var adapter = new UICollectionViewAdapter(collectionView); @@ -153,10 +141,6 @@ public override void ItemSelected(UICollectionView collectionView, NSIndexPath i /// /// An object that, when disposed, re-enables change /// notifications. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SuppressChangeNotifications uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SuppressChangeNotifications uses methods that may require unreferenced code")] -#endif public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this); /// diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSourceExtensions.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSourceExtensions.cs index cd7caeda3c..7a0d17e2a5 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSourceExtensions.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSourceExtensions.cs @@ -31,10 +31,8 @@ public static class ReactiveCollectionViewSourceExtensions /// the . /// Type of the view source. /// Type of the . -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindTo uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("BindTo uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + [RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflection-based invocation.")] public static IDisposable BindTo( this IObservable>> sectionsObservable, UICollectionView collectionView, @@ -66,10 +64,8 @@ public static IDisposable BindTo( /// the . /// Type of the source. /// Type of the . -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindTo uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("BindTo uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + [RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflection-based invocation.")] public static IDisposable BindTo( this IObservable sourceObservable, UICollectionView collectionView, @@ -102,10 +98,8 @@ public static IDisposable BindTo( /// the . /// Type of the source. /// Type of the . -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindTo uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("BindTo uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + [RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflection-based invocation.")] public static IDisposable BindTo( this IObservable sourceObservable, UICollectionView collectionView, diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveNavigationController.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveNavigationController.cs index 25cf896a66..496227b568 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveNavigationController.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveNavigationController.cs @@ -14,10 +14,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveNavigationController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveNavigationController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveNavigationController : UINavigationController, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate, IActivatableView { private readonly Subject _activated = new(); @@ -158,10 +154,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveNavigationController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveNavigationController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveNavigationController : ReactiveNavigationController, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactivePageViewController.cs b/src/ReactiveUI/Platforms/uikit-common/ReactivePageViewController.cs index 730b5f1958..66f1b08de5 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactivePageViewController.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactivePageViewController.cs @@ -14,10 +14,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactivePageViewController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactivePageViewController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactivePageViewController : UIPageViewController, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { private readonly Subject _activated = new(); @@ -183,10 +179,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactivePageViewController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactivePageViewController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactivePageViewController : ReactivePageViewController, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveTabBarController.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveTabBarController.cs index 1ffe91b898..c507e07226 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveTabBarController.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveTabBarController.cs @@ -14,10 +14,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTabBarController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTabBarController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveTabBarController : UITabBarController, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { private readonly Subject _activated = new(); @@ -139,10 +135,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTabBarController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTabBarController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveTabBarController : ReactiveTabBarController, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableView.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableView.cs index 0b4a20d8e3..20457b441f 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableView.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableView.cs @@ -16,10 +16,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTableView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTableView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveTableView : UITableView, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate, ICanForceManualActivation { private readonly Subject _activated = new(); @@ -140,10 +136,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTableView inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTableView inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveTableView : ReactiveTableView, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewCell.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewCell.cs index 9ec1a449e0..1c7ba07592 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewCell.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewCell.cs @@ -16,10 +16,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTableViewCell inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTableViewCell inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveTableViewCell : UITableViewCell, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { private readonly Subject _activated = new(); @@ -151,10 +147,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTableViewCell inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTableViewCell inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveTableViewCell : ReactiveTableViewCell, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewController.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewController.cs index 68b930273b..bb4c40e714 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewController.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewController.cs @@ -15,10 +15,6 @@ namespace ReactiveUI; /// (i.e. you can call RaiseAndSetIfChanged). /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTableViewController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTableViewController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveTableViewController : NSTableViewController, IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, ICanActivate { private readonly Subject _activated = new(); @@ -149,10 +145,6 @@ protected override void Dispose(bool disposing) /// The view model type. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "Classes with the same class names within.")] [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTableViewController inherits from ReactiveObject which uses extension methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTableViewController inherits from ReactiveObject which uses extension methods that may require unreferenced code")] -#endif public abstract class ReactiveTableViewController : ReactiveTableViewController, IViewFor where TViewModel : class { diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSource.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSource.cs index d44ceba6f8..5562d96990 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSource.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSource.cs @@ -18,10 +18,6 @@ namespace ReactiveUI; /// items are animated in and out as items are added. /// /// The source type. -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveTableViewSource uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveTableViewSource uses methods that may require unreferenced code")] -#endif public class ReactiveTableViewSource : UITableViewSource, IReactiveNotifyPropertyChanged>, IHandleObservableErrors, IReactiveObject { private readonly CommonReactiveSource> _commonSource; @@ -36,10 +32,6 @@ public class ReactiveTableViewSource : UITableViewSource, IReactiveNoti /// The cell key. /// The size hint. /// The initialize cell action. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveTableViewSource uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReactiveTableViewSource uses methods that may require unreferenced code")] -#endif public ReactiveTableViewSource(UITableView tableView, INotifyCollectionChanged collection, NSString cellKey, float sizeHint, Action? initializeCellAction = null) : this(tableView) => Data = [new TableSectionInformation(collection, cellKey, sizeHint, initializeCellAction) @@ -49,10 +41,6 @@ public ReactiveTableViewSource(UITableView tableView, INotifyCollectionChanged c /// Initializes a new instance of the class. /// /// The table view. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveTableViewSource uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReactiveTableViewSource uses methods that may require unreferenced code")] -#endif public ReactiveTableViewSource(UITableView tableView) { _adapter = new UITableViewAdapter(tableView); @@ -283,10 +271,6 @@ public override UIView GetViewForFooter(UITableView tableView, nint section) /// /// An object that, when disposed, re-enables change /// notifications. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SuppressChangeNotifications uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SuppressChangeNotifications uses methods that may require unreferenced code")] -#endif public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this); /// diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSourceExtensions.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSourceExtensions.cs index 0aa82b8083..9d50e757f0 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSourceExtensions.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSourceExtensions.cs @@ -31,10 +31,8 @@ public static class ReactiveTableViewSourceExtensions /// the . /// The source type. /// Type of the . -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindTo uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("BindTo uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + [RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflection-based invocation.")] public static IDisposable BindTo( this IObservable>> sectionsObservable, UITableView tableView, @@ -70,10 +68,8 @@ public static IDisposable BindTo( /// the . /// The source type. /// Type of the . -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindTo uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("BindTo uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + [RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflection-based invocation.")] public static IDisposable BindTo( this IObservable sourceObservable, UITableView tableView, @@ -108,10 +104,8 @@ public static IDisposable BindTo( /// the . /// The source type. /// Type of the . -#if NET6_0_OR_GREATER - [RequiresDynamicCode("BindTo uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("BindTo uses methods that may require unreferenced code")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + [RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflection-based invocation.")] public static IDisposable BindTo( this IObservable sourceObservable, UITableView tableView, diff --git a/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs b/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs index a0218b71f3..1ebab7cc3c 100644 --- a/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs +++ b/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs @@ -48,10 +48,9 @@ namespace ReactiveUI; /// /// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] -[RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] -#endif + +[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 class RoutedViewHost : ReactiveNavigationController { private readonly SerialDisposable _titleUpdater; @@ -62,10 +61,6 @@ public class RoutedViewHost : ReactiveNavigationController /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] -#endif public RoutedViewHost() { ViewContractObservable = Observable.Return(null); @@ -232,10 +227,6 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] -#endif private NSViewController? ResolveView(IRoutableViewModel? viewModel, string? contract) { if (viewModel is null) diff --git a/src/ReactiveUI/Platforms/uikit-common/UICollectionViewAdapter.cs b/src/ReactiveUI/Platforms/uikit-common/UICollectionViewAdapter.cs index 31b22fc048..d3a79057bb 100644 --- a/src/ReactiveUI/Platforms/uikit-common/UICollectionViewAdapter.cs +++ b/src/ReactiveUI/Platforms/uikit-common/UICollectionViewAdapter.cs @@ -13,10 +13,6 @@ namespace ReactiveUI; -#if NET6_0_OR_GREATER -[RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] -[RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif internal class UICollectionViewAdapter : IUICollViewAdapter, IDisposable { private readonly UICollectionView _view; diff --git a/src/ReactiveUI/Platforms/uikit-common/UITableViewAdapter.cs b/src/ReactiveUI/Platforms/uikit-common/UITableViewAdapter.cs index cf52973f8d..e85190f502 100644 --- a/src/ReactiveUI/Platforms/uikit-common/UITableViewAdapter.cs +++ b/src/ReactiveUI/Platforms/uikit-common/UITableViewAdapter.cs @@ -38,10 +38,6 @@ internal UITableViewAdapter(UITableView view) public UITableViewRowAnimation ReloadRowsAnimation { get; set; } = UITableViewRowAnimation.Automatic; -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReloadData uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReloadData uses methods that may require unreferenced code")] -#endif public void ReloadData() { ++_inFlightReloads; diff --git a/src/ReactiveUI/Helpers/CallerArgumentExpressionAttribute.cs b/src/ReactiveUI/Polyfills/CallerArgumentExpressionAttribute.cs similarity index 89% rename from src/ReactiveUI/Helpers/CallerArgumentExpressionAttribute.cs rename to src/ReactiveUI/Polyfills/CallerArgumentExpressionAttribute.cs index fb1b7f5ad6..2914c4488f 100644 --- a/src/ReactiveUI/Helpers/CallerArgumentExpressionAttribute.cs +++ b/src/ReactiveUI/Polyfills/CallerArgumentExpressionAttribute.cs @@ -3,6 +3,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +// Polyfill implementation adapted from SimonCropp/Polyfill +// https://github.com/SimonCropp/Polyfill #if !NET5_0_OR_GREATER using System.Diagnostics; @@ -12,6 +14,7 @@ namespace System.Runtime.CompilerServices; /// /// Indicates that a parameter captures the expression passed for another parameter as a string. +/// Modification of Using SimonCropp's polyfill's library. /// [ExcludeFromCodeCoverage] [DebuggerNonUserCode] diff --git a/src/ReactiveUI/Polyfills/DoesNotReturnIfAttribute.cs b/src/ReactiveUI/Polyfills/DoesNotReturnIfAttribute.cs new file mode 100644 index 0000000000..109174f661 --- /dev/null +++ b/src/ReactiveUI/Polyfills/DoesNotReturnIfAttribute.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2025 .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. + +// Polyfill implementation adapted from SimonCropp/Polyfill +// https://github.com/SimonCropp/Polyfill +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER + +using System.Diagnostics; + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Specifies that the method will not return if the associated +/// parameter is passed the specified value. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class DoesNotReturnIfAttribute : Attribute +{ + /// + /// Initializes a new instance of the + /// class with the specified parameter value. + /// + /// + /// The condition parameter value. Code after the method is considered unreachable + /// by diagnostics if the argument to the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => + ParameterValue = parameterValue; + + /// + /// Gets a value indicating whether code after the method is considered unreachable + /// by diagnostics if the argument to the associated parameter matches this value. + /// + public bool ParameterValue { get; } +} +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute))] +#endif diff --git a/src/ReactiveUI/Polyfills/DynamicallyAccessedMemberTypes.cs b/src/ReactiveUI/Polyfills/DynamicallyAccessedMemberTypes.cs new file mode 100644 index 0000000000..aef5a6f789 --- /dev/null +++ b/src/ReactiveUI/Polyfills/DynamicallyAccessedMemberTypes.cs @@ -0,0 +1,106 @@ +// Copyright (c) 2025 .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. + +// Polyfill implementation adapted from SimonCropp/Polyfill +// https://github.com/SimonCropp/Polyfill +#if !NET + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Specifies the types of members that are dynamically accessed. +/// +/// This enumeration has a attribute that allows a +/// bitwise combination of its member values. +/// +[Flags] +internal enum DynamicallyAccessedMemberTypes +{ + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all members. + /// + All = ~None +} + +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes))] +#endif diff --git a/src/ReactiveUI/Polyfills/DynamicallyAccessedMembersAttribute.cs b/src/ReactiveUI/Polyfills/DynamicallyAccessedMembersAttribute.cs new file mode 100644 index 0000000000..d25f053604 --- /dev/null +++ b/src/ReactiveUI/Polyfills/DynamicallyAccessedMembersAttribute.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2025 .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. + +// Polyfill implementation adapted from SimonCropp/Polyfill +// https://github.com/SimonCropp/Polyfill +#if !NET + +using System.Diagnostics; + +namespace System.Diagnostics.CodeAnalysis; + +using Targets = System.AttributeTargets; + +/// +/// Indicates that certain members on a specified are accessed dynamically, +/// for example through . +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + validOn: Targets.Class | + Targets.Field | + Targets.GenericParameter | + Targets.Interface | + Targets.Method | + Targets.Parameter | + Targets.Property | + Targets.ReturnValue | + Targets.Struct, + Inherited = false)] +internal sealed class DynamicallyAccessedMembersAttribute : Attribute +{ + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) => + MemberTypes = memberTypes; + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } +} + +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute))] +#endif diff --git a/src/ReactiveUI/Polyfills/IsExternalInit.cs b/src/ReactiveUI/Polyfills/IsExternalInit.cs new file mode 100644 index 0000000000..aad8d6f2fb --- /dev/null +++ b/src/ReactiveUI/Polyfills/IsExternalInit.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2025 .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. + +// Polyfill implementation adapted from SimonCropp/Polyfill +// https://github.com/SimonCropp/Polyfill +#if !NET + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace System.Runtime.CompilerServices; + +/// +/// Reserved to be used by the compiler for tracking metadata. This class should not be used by developers in source code. +/// Modification of Using SimonCropp's polyfill's library. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal static class IsExternalInit; + +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(IsExternalInit))] +#endif diff --git a/src/ReactiveUI/Polyfills/MaybeNullWhenAttribute.cs b/src/ReactiveUI/Polyfills/MaybeNullWhenAttribute.cs new file mode 100644 index 0000000000..d7d18bb18e --- /dev/null +++ b/src/ReactiveUI/Polyfills/MaybeNullWhenAttribute.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2025 .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. + +// Polyfill implementation adapted from SimonCropp/Polyfill +// https://github.com/SimonCropp/Polyfill +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER + +using System.Diagnostics; + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Specifies that when a method returns , +/// the parameter may be even if the corresponding type disallows it. +/// Modification of Using SimonCropp's polyfill's library. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class MaybeNullWhenAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// The return value condition. If the method returns this value, + /// the associated parameter may be . + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// + /// Gets a value indicating whether the return condition has been satisfied. + /// If the method returns this value, the associated parameter may be . + /// + public bool ReturnValue { get; } +} + +#else +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(MaybeNullWhenAttribute))] +#endif diff --git a/src/ReactiveUI/Polyfills/MemberNotNullAttribute.cs b/src/ReactiveUI/Polyfills/MemberNotNullAttribute.cs new file mode 100644 index 0000000000..6f5e5c2d99 --- /dev/null +++ b/src/ReactiveUI/Polyfills/MemberNotNullAttribute.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2025 .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 !NET + +using System.Diagnostics; + +using Targets = System.AttributeTargets; + +/// +/// Specifies that the method or property will ensure that the listed field and property members have +/// not- values. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + validOn: Targets.Method | + Targets.Property, + Inherited = false, + AllowMultiple = true)] +internal sealed class MemberNotNullAttribute : + Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// Field or property member name. + public MemberNotNullAttribute(string member) => + Members = [member]; + + /// + /// Initializes a new instance of the class. + /// + /// Field or property member names. + public MemberNotNullAttribute(params string[] members) => + Members = members; + + /// + /// Gets field or property member names. + /// + public string[] Members { get; } +} + +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.MemberNotNullAttribute))] +#endif diff --git a/src/ReactiveUI/Helpers/NotNullAttribute.cs b/src/ReactiveUI/Polyfills/NotNullAttribute.cs similarity index 83% rename from src/ReactiveUI/Helpers/NotNullAttribute.cs rename to src/ReactiveUI/Polyfills/NotNullAttribute.cs index c0113756ba..6400c56764 100644 --- a/src/ReactiveUI/Helpers/NotNullAttribute.cs +++ b/src/ReactiveUI/Polyfills/NotNullAttribute.cs @@ -3,16 +3,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +// Polyfill implementation adapted from SimonCropp/Polyfill +// https://github.com/SimonCropp/Polyfill #if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - namespace System.Diagnostics.CodeAnalysis; /// /// Specifies that an output is not even if the /// corresponding type allows it. +/// Modification of Using SimonCropp's polyfill's library. /// [ExcludeFromCodeCoverage] [DebuggerNonUserCode] @@ -22,8 +22,4 @@ namespace System.Diagnostics.CodeAnalysis; AttributeTargets.Property | AttributeTargets.ReturnValue)] internal sealed class NotNullAttribute : Attribute; -#else -using System.Runtime.CompilerServices; - -[assembly: TypeForwardedTo(typeof(NotNullAttribute))] #endif diff --git a/src/ReactiveUI/Polyfills/NotNullWhenAttribute.cs b/src/ReactiveUI/Polyfills/NotNullWhenAttribute.cs new file mode 100644 index 0000000000..f5292b4e26 --- /dev/null +++ b/src/ReactiveUI/Polyfills/NotNullWhenAttribute.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2025 .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. + +// Polyfill implementation adapted from SimonCropp/Polyfill +// https://github.com/SimonCropp/Polyfill +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Specifies that when a method returns , +/// the parameter will not be even if the corresponding type allows it. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class NotNullWhenAttribute : + Attribute +{ + /// + /// Initializes a new instance of the class. + /// + public NotNullWhenAttribute(bool returnValue) => + ReturnValue = returnValue; + + /// + /// Gets a value indicating whether it is a return value condition. + /// If the method returns this value, the associated parameter will not be . + /// + public bool ReturnValue { get; } +} + +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.NotNullWhenAttribute))] +#endif diff --git a/src/ReactiveUI/Polyfills/RequiresDynamicCodeAttribute.cs b/src/ReactiveUI/Polyfills/RequiresDynamicCodeAttribute.cs new file mode 100644 index 0000000000..e576234453 --- /dev/null +++ b/src/ReactiveUI/Polyfills/RequiresDynamicCodeAttribute.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2025 .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. + +// Polyfill implementation adapted from Simon Cropp's Polyfill library +// https://github.com/SimonCropp/Polyfill +#if !NET7_0_OR_GREATER + +#nullable enable + +using Targets = System.AttributeTargets; + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Indicates that the specified method requires the ability to generate new code at runtime, +/// for example through . +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + validOn: Targets.Method | + Targets.Constructor | + Targets.Class, + Inherited = false)] +internal sealed class RequiresDynamicCodeAttribute : + Attribute +{ + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + public RequiresDynamicCodeAttribute(string message) => + Message = message; + + /// + /// Gets or sets a value indicating whether the annotation should not apply to static members. + /// + public bool ExcludeStatics { get; set; } + + /// + /// Gets a message that contains information about the usage of dynamic code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires dynamic code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } +} +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute))] +#endif diff --git a/src/ReactiveUI/Polyfills/RequiresUnreferencedCodeAttribute.cs b/src/ReactiveUI/Polyfills/RequiresUnreferencedCodeAttribute.cs new file mode 100644 index 0000000000..481e7d030b --- /dev/null +++ b/src/ReactiveUI/Polyfills/RequiresUnreferencedCodeAttribute.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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. + +// Polyfill implementation adapted from Simon Cropp's Polyfill library +// https://github.com/SimonCropp/Polyfill +#if !NET + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Indicates that the specified method requires dynamic access to code that is not referenced +/// statically, for example through . +/// +/// +/// Link: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.requiresunreferencedcodeattribute. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + AttributeTargets.Method | + AttributeTargets.Constructor | + AttributeTargets.Class, + Inherited = false)] +internal sealed class RequiresUnreferencedCodeAttribute : Attribute +{ + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// A message that contains information about the usage of unreferenced code. + public RequiresUnreferencedCodeAttribute(string message) => + Message = message; + + /// + /// Gets a message that contains information about the usage of unreferenced code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires unreferenced code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } +} + +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute))] +#endif diff --git a/src/ReactiveUI/Polyfills/UnconditionalSuppressMessageAttribute.cs b/src/ReactiveUI/Polyfills/UnconditionalSuppressMessageAttribute.cs new file mode 100644 index 0000000000..143bbe2309 --- /dev/null +++ b/src/ReactiveUI/Polyfills/UnconditionalSuppressMessageAttribute.cs @@ -0,0 +1,74 @@ +// Copyright (c) 2025 .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. + +// Polyfill implementation adapted from Simon Cropp's Polyfill library +// https://github.com/SimonCropp/Polyfill +#if !NET + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a +/// single code artifact. +/// +/// +/// Link: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.unconditionalsuppressmessageattribute. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + AttributeTargets.All, + Inherited = false, + AllowMultiple = true)] +internal sealed class UnconditionalSuppressMessageAttribute : Attribute +{ + /// + /// Initializes a new instance of the + /// class, specifying the category of the tool and the identifier for an analysis rule. + /// + /// The category identifying the classification of the attribute. + /// The identifier of the analysis tool rule to be suppressed. + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + public string Category { get; } + + /// + /// Gets the identifier of the analysis tool rule to be suppressed. + /// + public string CheckId { get; } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + public string? Scope { get; set; } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + public string? Target { get; set; } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + public string? MessageId { get; set; } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string? Justification { get; set; } +} + +#else +using System.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute))] +#endif diff --git a/src/ReactiveUI/ReactiveCommand/CombinedReactiveCommand.cs b/src/ReactiveUI/ReactiveCommand/CombinedReactiveCommand.cs index 7559dde91f..d0a5a56a7b 100644 --- a/src/ReactiveUI/ReactiveCommand/CombinedReactiveCommand.cs +++ b/src/ReactiveUI/ReactiveCommand/CombinedReactiveCommand.cs @@ -45,10 +45,6 @@ public class CombinedReactiveCommand : ReactiveCommandBaseThe scheduler where to dispatch the output from the command. /// Fires when required arguments are null. /// Fires if the child commands container is empty. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("CombinedReactiveCommand uses RxApp and ReactiveCommand which require dynamic code generation.")] - [RequiresUnreferencedCode("CombinedReactiveCommand uses RxApp and ReactiveCommand which may require unreferenced code.")] -#endif protected internal CombinedReactiveCommand( IEnumerable> childCommands, IObservable? canExecute, @@ -56,7 +52,7 @@ protected internal CombinedReactiveCommand( { ArgumentExceptionHelper.ThrowIfNull(childCommands); - _outputScheduler = outputScheduler ?? RxApp.MainThreadScheduler; + _outputScheduler = outputScheduler ?? RxSchedulers.MainThreadScheduler; var childCommandsArray = childCommands.ToArray(); @@ -65,7 +61,7 @@ protected internal CombinedReactiveCommand( throw new ArgumentException("No child commands provided.", nameof(childCommands)); } - _exceptions = new ScheduledSubject(_outputScheduler, RxApp.DefaultExceptionHandler); + _exceptions = new ScheduledSubject(_outputScheduler, RxState.DefaultExceptionHandler); var canChildrenExecute = childCommandsArray.Select(x => x.CanExecute) .CombineLatest() diff --git a/src/ReactiveUI/ReactiveCommand/ReactiveCommand.cs b/src/ReactiveUI/ReactiveCommand/ReactiveCommand.cs index 551cfcf174..0527496531 100644 --- a/src/ReactiveUI/ReactiveCommand/ReactiveCommand.cs +++ b/src/ReactiveUI/ReactiveCommand/ReactiveCommand.cs @@ -72,10 +72,6 @@ public static class ReactiveCommand /// The ReactiveCommand instance. /// /// execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand Create( Action execute, IObservable? canExecute = null, @@ -107,10 +103,6 @@ public static ReactiveCommand Create( /// The ReactiveCommand instance. /// /// execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateRunInBackground( Action execute, IObservable? canExecute = null, @@ -119,7 +111,7 @@ public static ReactiveCommand CreateRunInBackground( { ArgumentExceptionHelper.ThrowIfNull(execute); - return CreateFromObservable(() => Observable.Start(execute, backgroundScheduler ?? RxApp.TaskpoolScheduler), canExecute, outputScheduler); + return CreateFromObservable(() => Observable.Start(execute, backgroundScheduler ?? RxSchedulers.TaskpoolScheduler), canExecute, outputScheduler); } /// @@ -134,10 +126,6 @@ public static ReactiveCommand CreateRunInBackground( /// The ReactiveCommand instance. /// /// execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand Create( Func execute, IObservable? canExecute = null, @@ -170,10 +158,6 @@ public static ReactiveCommand Create( /// The ReactiveCommand instance. /// /// execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateRunInBackground( Func execute, IObservable? canExecute = null, @@ -182,7 +166,7 @@ public static ReactiveCommand CreateRunInBackground( { ArgumentExceptionHelper.ThrowIfNull(execute); - return CreateFromObservable(() => Observable.Start(execute, backgroundScheduler ?? RxApp.TaskpoolScheduler), canExecute, outputScheduler); + return CreateFromObservable(() => Observable.Start(execute, backgroundScheduler ?? RxSchedulers.TaskpoolScheduler), canExecute, outputScheduler); } /// @@ -196,10 +180,6 @@ public static ReactiveCommand CreateRunInBackground( /// The ReactiveCommand instance. /// /// execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand Create( Action execute, IObservable? canExecute = null, @@ -232,10 +212,6 @@ public static ReactiveCommand Create( /// The ReactiveCommand instance. /// /// execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateRunInBackground( Action execute, IObservable? canExecute = null, @@ -244,7 +220,7 @@ public static ReactiveCommand CreateRunInBackground( { ArgumentExceptionHelper.ThrowIfNull(execute); - return CreateFromObservable(p => Observable.Start(() => execute(p), backgroundScheduler ?? RxApp.TaskpoolScheduler), canExecute, outputScheduler); + return CreateFromObservable(p => Observable.Start(() => execute(p), backgroundScheduler ?? RxSchedulers.TaskpoolScheduler), canExecute, outputScheduler); } /// @@ -260,10 +236,6 @@ public static ReactiveCommand CreateRunInBackground( /// The ReactiveCommand instance. /// /// execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand Create( Func execute, IObservable? canExecute = null, @@ -297,10 +269,6 @@ public static ReactiveCommand Create( /// The ReactiveCommand instance. /// /// execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateRunInBackground( Func execute, IObservable? canExecute = null, @@ -309,7 +277,7 @@ public static ReactiveCommand CreateRunInBackground(p => Observable.Start(() => execute(p), backgroundScheduler ?? RxApp.TaskpoolScheduler), canExecute, outputScheduler); + return CreateFromObservable(p => Observable.Start(() => execute(p), backgroundScheduler ?? RxSchedulers.TaskpoolScheduler), canExecute, outputScheduler); } /// @@ -334,10 +302,6 @@ public static ReactiveCommand CreateRunInBackground /// The type of the command's result. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("CreateCombined uses CombinedReactiveCommand which requires dynamic code generation.")] - [RequiresUnreferencedCode("CreateCombined uses CombinedReactiveCommand which may require unreferenced code.")] -#endif public static CombinedReactiveCommand CreateCombined( IEnumerable> childCommands, IObservable? canExecute = null, @@ -366,10 +330,6 @@ public static CombinedReactiveCommand CreateCombined /// The type of the command's result. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromObservable( Func> execute, IObservable? canExecute = null, @@ -404,10 +364,6 @@ public static ReactiveCommand CreateFromObservable( /// /// The type of the command's result. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromObservable( Func> execute, IObservable? canExecute = null, @@ -439,10 +395,6 @@ public static ReactiveCommand CreateFromObservable /// The type of the command's result. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromTask( Func> execute, IObservable? canExecute = null, @@ -471,10 +423,6 @@ public static ReactiveCommand CreateFromTask( /// /// The type of the command's result. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromTask( Func> execute, IObservable? canExecute = null, @@ -500,10 +448,6 @@ public static ReactiveCommand CreateFromTask( /// /// The ReactiveCommand instance. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromTask( Func execute, IObservable? canExecute = null, @@ -529,10 +473,6 @@ public static ReactiveCommand CreateFromTask( /// /// The ReactiveCommand instance. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromTask( Func execute, IObservable? canExecute = null, @@ -564,10 +504,6 @@ public static ReactiveCommand CreateFromTask( /// /// The type of the command's result. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromTask( Func> execute, IObservable? canExecute = null, @@ -602,10 +538,6 @@ public static ReactiveCommand CreateFromTask( /// /// The type of the command's result. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromTask( Func> execute, IObservable? canExecute = null, @@ -637,10 +569,6 @@ public static ReactiveCommand CreateFromTask( /// /// The type of the parameter passed through to command execution. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromTask( Func execute, IObservable? canExecute = null, @@ -672,10 +600,6 @@ public static ReactiveCommand CreateFromTask( /// /// The type of the parameter passed through to command execution. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif public static ReactiveCommand CreateFromTask( Func execute, IObservable? canExecute = null, @@ -701,10 +625,6 @@ public static ReactiveCommand CreateFromTask( /// The ReactiveCommand instance. /// /// execute. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif internal static ReactiveCommand CreateFromObservableCancellable( Func Result, Action Cancel)>> execute, IObservable? canExecute = null, @@ -739,10 +659,6 @@ internal static ReactiveCommand CreateFromObservableCancellable /// /// The type of the command's result. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif internal static ReactiveCommand CreateFromObservableCancellable( Func Result, Action Cancel)>> execute, IObservable? canExecute = null, @@ -803,18 +719,14 @@ public class ReactiveCommand : ReactiveCommandBase /// Thrown if any dependent parameters are null. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif protected internal ReactiveCommand( Func Result, Action Cancel)>> execute, IObservable? canExecute, IScheduler? outputScheduler) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _outputScheduler = outputScheduler ?? RxApp.MainThreadScheduler; - _exceptions = new ScheduledSubject(_outputScheduler, RxApp.DefaultExceptionHandler); + _outputScheduler = outputScheduler ?? RxSchedulers.MainThreadScheduler; + _exceptions = new ScheduledSubject(_outputScheduler, RxState.DefaultExceptionHandler); _executionInfo = new Subject(); _synchronizedExecutionInfo = Subject.Synchronize(_executionInfo, _outputScheduler); _isExecuting = _synchronizedExecutionInfo @@ -861,10 +773,6 @@ protected internal ReactiveCommand( /// execute. /// /// Thrown if any dependent parameters are null. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] -#endif protected internal ReactiveCommand( Func> execute, IObservable? canExecute, @@ -883,7 +791,7 @@ protected internal ReactiveCommand( }); }, canExecute, - outputScheduler ?? RxApp.MainThreadScheduler) + outputScheduler ?? RxSchedulers.MainThreadScheduler) { } diff --git a/src/ReactiveUI/ReactiveCommand/ReactiveCommandMixins.cs b/src/ReactiveUI/ReactiveCommand/ReactiveCommandMixins.cs index 7f9f15cb03..15145b36e4 100644 --- a/src/ReactiveUI/ReactiveCommand/ReactiveCommandMixins.cs +++ b/src/ReactiveUI/ReactiveCommand/ReactiveCommandMixins.cs @@ -88,10 +88,7 @@ command is null /// The expression to reference the Command. /// An object that when disposes, disconnects the Observable /// from the command. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("InvokeCommand uses WhenAnyValue which requires dynamic code generation for expression tree analysis.")] - [RequiresUnreferencedCode("InvokeCommand uses WhenAnyValue which may reference members that could be trimmed.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IDisposable InvokeCommand(this IObservable item, TTarget? target, Expression> commandProperty) where TTarget : class { @@ -125,10 +122,7 @@ public static IDisposable InvokeCommand(this IObservable item, TT /// The expression to reference the Command. /// An object that when disposes, disconnects the Observable /// from the command. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("InvokeCommand uses WhenAnyValue which requires dynamic code generation for expression tree analysis.")] - [RequiresUnreferencedCode("InvokeCommand uses WhenAnyValue which may reference members that could be trimmed.")] -#endif + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static IDisposable InvokeCommand(this IObservable item, TTarget? target, Expression?>> commandProperty) where TTarget : class { diff --git a/src/ReactiveUI/ReactiveObject/IReactiveObjectExtensions.cs b/src/ReactiveUI/ReactiveObject/IReactiveObjectExtensions.cs index d64f06b200..b723823051 100644 --- a/src/ReactiveUI/ReactiveObject/IReactiveObjectExtensions.cs +++ b/src/ReactiveUI/ReactiveObject/IReactiveObjectExtensions.cs @@ -258,7 +258,7 @@ internal static IDisposable DelayChangeNotifications(this TSender react private class ExtensionState : IExtensionState where TSender : IReactiveObject { - private readonly Lazy> _thrownExceptions = new(static () => new ScheduledSubject(Scheduler.Immediate, RxApp.DefaultExceptionHandler)); + private readonly Lazy> _thrownExceptions = new(static () => new ScheduledSubject(Scheduler.Immediate, RxState.DefaultExceptionHandler)); private readonly Lazy> _startOrStopDelayingChangeNotifications = new(); private readonly TSender _sender; private readonly Lazy<(ISubject> subject, IObservable> observable)> _changing; diff --git a/src/ReactiveUI/ReactiveObject/ReactiveObject.cs b/src/ReactiveUI/ReactiveObject/ReactiveObject.cs index e7d2b46178..5d52b5e54e 100644 --- a/src/ReactiveUI/ReactiveObject/ReactiveObject.cs +++ b/src/ReactiveUI/ReactiveObject/ReactiveObject.cs @@ -100,10 +100,6 @@ void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChangedHandler?.Invoke(this, args); /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - [RequiresDynamicCode("This method uses reflection to access properties by name.")] -#endif public IDisposable SuppressChangeNotifications() => // TODO: Create Test IReactiveObjectExtensions.SuppressChangeNotifications(this); diff --git a/src/ReactiveUI/ReactiveObject/ReactiveRecord.cs b/src/ReactiveUI/ReactiveObject/ReactiveRecord.cs index 4dbc99077e..32c78f47bd 100644 --- a/src/ReactiveUI/ReactiveObject/ReactiveRecord.cs +++ b/src/ReactiveUI/ReactiveObject/ReactiveRecord.cs @@ -19,10 +19,6 @@ public record ReactiveRecord : IReactiveNotifyPropertyChanged, /// /// Initializes a new instance of the class. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveRecord constructor uses extension methods that require dynamic code generation")] - [RequiresUnreferencedCode("ReactiveRecord constructor uses extension methods that may require unreferenced code")] -#endif public ReactiveRecord() { } @@ -101,10 +97,6 @@ public event PropertyChangedEventHandler? PropertyChanged void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChangedHandler?.Invoke(this, args); /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SuppressChangeNotifications uses extension methods that require dynamic code generation")] - [RequiresUnreferencedCode("SuppressChangeNotifications uses extension methods that may require unreferenced code")] -#endif public IDisposable SuppressChangeNotifications() => // TODO: Create Test IReactiveObjectExtensions.SuppressChangeNotifications(this); @@ -112,10 +104,6 @@ public IDisposable SuppressChangeNotifications() => // TODO: Create Test /// Determines if change notifications are enabled or not. /// /// A value indicating whether change notifications are enabled. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("AreChangeNotificationsEnabled uses extension methods that require dynamic code generation")] - [RequiresUnreferencedCode("AreChangeNotificationsEnabled uses extension methods that may require unreferenced code")] -#endif public bool AreChangeNotificationsEnabled() => // TODO: Create Test IReactiveObjectExtensions.AreChangeNotificationsEnabled(this); @@ -123,10 +111,6 @@ public bool AreChangeNotificationsEnabled() => // TODO: Create Test /// Delays notifications until the return IDisposable is disposed. /// /// A disposable which when disposed will send delayed notifications. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("DelayChangeNotifications uses extension methods that require dynamic code generation")] - [RequiresUnreferencedCode("DelayChangeNotifications uses extension methods that may require unreferenced code")] -#endif public IDisposable DelayChangeNotifications() => // TODO: Create Test IReactiveObjectExtensions.DelayChangeNotifications(this); } diff --git a/src/ReactiveUI/ReactiveProperty/IReactiveProperty.cs b/src/ReactiveUI/ReactiveProperty/IReactiveProperty.cs index 53fc8ce327..a2d688faea 100644 --- a/src/ReactiveUI/ReactiveProperty/IReactiveProperty.cs +++ b/src/ReactiveUI/ReactiveProperty/IReactiveProperty.cs @@ -38,9 +38,5 @@ public interface IReactiveProperty : IObservable, ICancelable, INotifyDat /// /// Refreshes this instance. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Refresh uses RaisePropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("Refresh uses RaisePropertyChanged which may require unreferenced code")] -#endif void Refresh(); } diff --git a/src/ReactiveUI/ReactiveProperty/ReactiveProperty.cs b/src/ReactiveUI/ReactiveProperty/ReactiveProperty.cs index 0ff7652171..9c5759998e 100644 --- a/src/ReactiveUI/ReactiveProperty/ReactiveProperty.cs +++ b/src/ReactiveUI/ReactiveProperty/ReactiveProperty.cs @@ -4,6 +4,7 @@ // See the LICENSE file in the project root for full license information. using System.Collections; +using System.Runtime.CompilerServices; namespace ReactiveUI; @@ -14,36 +15,93 @@ namespace ReactiveUI; /// /// [DataContract] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("ReactiveProperty initialization uses ReactiveObject and RxApp which require dynamic code generation")] -[RequiresUnreferencedCode("ReactiveProperty initialization uses ReactiveObject and RxApp which may require unreferenced code")] -#endif public class ReactiveProperty : ReactiveObject, IReactiveProperty { + /// + /// The scheduler used to marshal validation and error notifications. + /// private readonly IScheduler _scheduler; + + /// + /// Holds disposables associated with the instance lifetime. + /// private readonly CompositeDisposable _disposables = []; + + /// + /// The equality comparer used to compare incoming values to the current value. + /// private readonly EqualityComparer _checkIf = EqualityComparer.Default; + + /// + /// Publishes value changes to the validation pipeline. + /// private readonly Subject _checkValidation = new(); + + /// + /// Publishes "refresh" signals for the current value (used to force emission even when the value is unchanged). + /// private readonly Subject _valueRefereshed = new(); + + /// + /// Publishes changes without relying on reflection-based property observation. + /// + /// + /// + /// This subject replaces the prior WhenAnyValue(nameof(Value)) path and provides the source stream used by + /// . + /// + /// + /// The subject is disposed with the instance; emission sites guard against disposal using . + /// + /// + private readonly BehaviorSubject _valueChanged; + + /// + /// Holds the active validation subscription, if any. + /// private readonly SerialDisposable _validationDisposable = new(); + + /// + /// Lazily created subject that publishes the current error sequence. + /// private readonly Lazy> _errorChanged; + + /// + /// Stores validators registered via . + /// private readonly Lazy, IObservable>>> _validatorStore = new(static () => []); + + /// + /// The number of initial values to skip for subscriptions created by . + /// private readonly int _skipCurrentValue; + + /// + /// Indicates whether applies DistinctUntilChanged. + /// private readonly bool _isDistinctUntilChanged; + + /// + /// The shared observable backing . + /// private IObservable? _observable; + + /// + /// The current value backing . + /// private T? _value; + + /// + /// The current validation errors, if any. + /// private IEnumerable? _currentErrors; /// /// Initializes a new instance of the class. /// The Value will be default(T). DistinctUntilChanged is true. Current Value is published on subscribe. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] -#endif public ReactiveProperty() - : this(default, RxApp.TaskpoolScheduler, false, false) + : this(default, RxSchedulers.TaskpoolScheduler, false, false) { } @@ -52,12 +110,8 @@ public ReactiveProperty() /// The Value will be initialValue. DistinctUntilChanged is true. Current Value is published on subscribe. /// /// The initial value. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] -#endif public ReactiveProperty(T? initialValue) - : this(initialValue, RxApp.TaskpoolScheduler, false, false) + : this(initialValue, RxSchedulers.TaskpoolScheduler, false, false) { } @@ -67,12 +121,8 @@ public ReactiveProperty(T? initialValue) /// The initial value. /// if set to true [skip current value on subscribe]. /// if set to true [allow duplicate concurrent values]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] -#endif public ReactiveProperty(T? initialValue, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) - : this(initialValue, RxApp.TaskpoolScheduler, skipCurrentValueOnSubscribe, allowDuplicateValues) + : this(initialValue, RxSchedulers.TaskpoolScheduler, skipCurrentValueOnSubscribe, allowDuplicateValues) { } @@ -83,17 +133,17 @@ public ReactiveProperty(T? initialValue, bool skipCurrentValueOnSubscribe, bool /// The scheduler. /// if set to true [skip current value on subscribe]. /// if set to true [allow duplicate concurrent values]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ReactiveProperty initialization uses ReactiveObject and RxApp which require dynamic code generation")] - [RequiresUnreferencedCode("ReactiveProperty initialization uses ReactiveObject and RxApp which may require unreferenced code")] -#endif + /// Thrown if is . public ReactiveProperty(T? initialValue, IScheduler? scheduler, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { _skipCurrentValue = skipCurrentValueOnSubscribe ? 1 : 0; _isDistinctUntilChanged = !allowDuplicateValues; _value = initialValue; - _scheduler = scheduler ?? RxApp.TaskpoolScheduler; + _scheduler = scheduler ?? RxSchedulers.TaskpoolScheduler; + + _valueChanged = new BehaviorSubject(initialValue); _errorChanged = new Lazy>(() => new BehaviorSubject(GetErrors(null))); + GetSubscription(); } @@ -122,6 +172,7 @@ public T? Value { if (!_isDistinctUntilChanged) { + // Preserve existing semantics: identical assignment produces a "refresh" emission when duplicates are allowed. _valueRefereshed.OnNext(_value); } @@ -159,31 +210,31 @@ public T? Value /// /// Creates a new instance of ReactiveProperty without requiring RequiresUnreferencedCode attributes. - /// Uses RxApp.TaskpoolScheduler as the default scheduler. + /// Uses RxSchedulers.TaskpoolScheduler as the default scheduler. /// /// A new ReactiveProperty instance. public static ReactiveProperty Create() - => new(default, RxApp.TaskpoolScheduler, false, false); + => new(default, RxSchedulers.TaskpoolScheduler, false, false); /// /// Creates a new instance of ReactiveProperty with an initial value without requiring RequiresUnreferencedCode attributes. - /// Uses RxApp.TaskpoolScheduler as the default scheduler. + /// Uses RxSchedulers.TaskpoolScheduler as the default scheduler. /// /// The initial value. /// A new ReactiveProperty instance. public static ReactiveProperty Create(T? initialValue) - => new(initialValue, RxApp.TaskpoolScheduler, false, false); + => new(initialValue, RxSchedulers.TaskpoolScheduler, false, false); /// /// Creates a new instance of ReactiveProperty with configuration options without requiring RequiresUnreferencedCode attributes. - /// Uses RxApp.TaskpoolScheduler as the default scheduler. + /// Uses RxSchedulers.TaskpoolScheduler as the default scheduler. /// /// The initial value. /// if set to true [skip current value on subscribe]. /// if set to true [allow duplicate concurrent values]. /// A new ReactiveProperty instance. public static ReactiveProperty Create(T? initialValue, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) - => new(initialValue, RxApp.TaskpoolScheduler, skipCurrentValueOnSubscribe, allowDuplicateValues); + => new(initialValue, RxSchedulers.TaskpoolScheduler, skipCurrentValueOnSubscribe, allowDuplicateValues); /// /// Creates a new instance of ReactiveProperty with a custom scheduler without requiring RequiresUnreferencedCode attributes. @@ -193,6 +244,7 @@ public static ReactiveProperty Create(T? initialValue, bool skipCurrentValueO /// if set to true [skip current value on subscribe]. /// if set to true [allow duplicate concurrent values]. /// A new ReactiveProperty instance. + /// Thrown if is . public static ReactiveProperty Create(T? initialValue, IScheduler scheduler, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) => new(initialValue, scheduler, skipCurrentValueOnSubscribe, allowDuplicateValues); @@ -204,10 +256,6 @@ public static ReactiveProperty Create(T? initialValue, IScheduler scheduler, /// /// Self. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] -#endif public ReactiveProperty AddValidationError(Func, IObservable> validator, bool ignoreInitialError = false) { _validatorStore.Value.Add(validator); @@ -263,10 +311,6 @@ public ReactiveProperty AddValidationError(Func, IObservable< /// /// Self. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] -#endif public ReactiveProperty AddValidationError(Func, IObservable> validator, bool ignoreInitialError = false) => AddValidationError(xs => validator(xs).Select(x => (IEnumerable?)x), ignoreInitialError); @@ -278,10 +322,6 @@ public ReactiveProperty AddValidationError(Func, IObservable< /// /// Self. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] -#endif public ReactiveProperty AddValidationError(Func> validator, bool ignoreInitialError = false) => AddValidationError(xs => xs.SelectMany(x => validator(x)), ignoreInitialError); @@ -293,10 +333,6 @@ public ReactiveProperty AddValidationError(Func> valid /// /// Self. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] -#endif public ReactiveProperty AddValidationError(Func> validator, bool ignoreInitialError = false) => AddValidationError(xs => xs.SelectMany(x => validator(x)), ignoreInitialError); @@ -308,10 +344,6 @@ public ReactiveProperty AddValidationError(Func> validator, /// /// Self. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] -#endif public ReactiveProperty AddValidationError(Func validator, bool ignoreInitialError = false) => AddValidationError(xs => xs.Select(x => validator(x)), ignoreInitialError); @@ -323,16 +355,17 @@ public ReactiveProperty AddValidationError(Func validator, /// /// Self. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] -#endif public ReactiveProperty AddValidationError(Func validator, bool ignoreInitialError = false) => AddValidationError(xs => xs.Select(x => validator(x)), ignoreInitialError); /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// +#if NET8_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public void Dispose() { Dispose(true); @@ -342,15 +375,16 @@ public void Dispose() /// /// Check validation. /// +#if NET8_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public void CheckValidation() => _checkValidation.OnNext(_value); /// /// Invoke OnNext. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Refresh uses RaisePropertyChanged which requires dynamic code generation")] - [RequiresUnreferencedCode("Refresh uses RaisePropertyChanged which may require unreferenced code")] -#endif public void Refresh() { SetValue(_value); @@ -405,9 +439,14 @@ protected virtual void Dispose(bool disposing) if (_disposables?.IsDisposed == false && disposing) { _disposables?.Dispose(); + _checkValidation.Dispose(); _valueRefereshed.Dispose(); _validationDisposable.Dispose(); + + _valueChanged.OnCompleted(); + _valueChanged.Dispose(); + if (_errorChanged.IsValueCreated) { _errorChanged.Value.OnCompleted(); @@ -416,26 +455,50 @@ protected virtual void Dispose(bool disposing) } } + /// + /// Sets the backing value, publishes to the validation stream, and publishes to the value-change stream. + /// + /// The new value. +#if NET8_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif private void SetValue(T? value) { _value = value; + if (!IsDisposed) { _checkValidation.OnNext(value); + _valueChanged.OnNext(value); } } + /// + /// Initializes the shared subscription backing . + /// + /// + /// + /// The stream is built from a value source that does not require reflection-based property observation. + /// + /// + /// Duplicate suppression (when enabled) applies only to value-change emissions, while explicit refresh emissions + /// always flow through to subscribers. + /// + /// private void GetSubscription() { - _observable = this.WhenAnyValue, T>(nameof(Value)) - .Skip(_skipCurrentValue); + IObservable source = _valueChanged; + + source = source.Skip(_skipCurrentValue); if (_isDistinctUntilChanged) { - _observable = _observable.DistinctUntilChanged(); + source = source.DistinctUntilChanged(); } - _observable = _observable + _observable = source .Merge(_valueRefereshed) .Replay(1) .RefCount() diff --git a/src/ReactiveUI/ReactiveProperty/ReactivePropertyMixins.cs b/src/ReactiveUI/ReactiveProperty/ReactivePropertyMixins.cs index 4b1ecbc118..07f18b1701 100644 --- a/src/ReactiveUI/ReactiveProperty/ReactivePropertyMixins.cs +++ b/src/ReactiveUI/ReactiveProperty/ReactivePropertyMixins.cs @@ -30,10 +30,11 @@ public static class ReactivePropertyMixins /// or /// self. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("The method uses DataAnnotations validation which requires dynamic code generation.")] - [RequiresUnreferencedCode("The method uses DataAnnotations validation which may require unreferenced code.")] -#endif + /// + /// This method uses DataAnnotations validation which requires reflection and is not compatible with AOT compilation. + /// For AOT scenarios, use manual validation instead. + /// + [RequiresUnreferencedCode("DataAnnotations validation uses reflection to discover attributes and is not trim-safe. Use manual validation for AOT scenarios.")] public static ReactiveProperty AddValidation(this ReactiveProperty self, Expression?>> selfSelector) { ArgumentExceptionHelper.ThrowIfNull(selfSelector); @@ -43,7 +44,9 @@ public static ReactiveProperty AddValidation(this ReactiveProperty self var propertyInfo = (PropertyInfo)memberExpression.Member; var display = propertyInfo.GetCustomAttribute(); var attrs = propertyInfo.GetCustomAttributes().ToArray(); - var context = new ValidationContext(self) + + // Use the AOT-compatible constructor that doesn't require reflection for type discovery + var context = new ValidationContext(self, serviceProvider: null, items: null) { DisplayName = display?.GetName() ?? propertyInfo.Name, MemberName = nameof(ReactiveProperty.Value), diff --git a/src/ReactiveUI/ReactiveUI.csproj b/src/ReactiveUI/ReactiveUI.csproj index 951a1a665f..c172e32277 100644 --- a/src/ReactiveUI/ReactiveUI.csproj +++ b/src/ReactiveUI/ReactiveUI.csproj @@ -99,7 +99,11 @@ + + + + @@ -107,6 +111,7 @@ + @@ -114,6 +119,7 @@ + diff --git a/src/ReactiveUI/Registration/DependencyResolverRegistrar.cs b/src/ReactiveUI/Registration/DependencyResolverRegistrar.cs new file mode 100644 index 0000000000..cf2a85505e --- /dev/null +++ b/src/ReactiveUI/Registration/DependencyResolverRegistrar.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2025 .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; + +/// +/// An AOT-friendly implementation of that wraps +/// an from Splat. +/// +/// +/// Initializes a new instance of the class. +/// +/// The dependency resolver to wrap. +internal sealed class DependencyResolverRegistrar(IMutableDependencyResolver resolver) : IRegistrar +{ + private readonly IMutableDependencyResolver _resolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); + + /// + public void RegisterConstant(Func factory, string? contract = null) + where TService : class + { + ArgumentExceptionHelper.ThrowIfNull(factory); + if (contract is null) + { + _resolver.RegisterConstant(factory()); + } + else + { + _resolver.RegisterConstant(factory(), contract); + } + } + + /// + public void RegisterLazySingleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TService>(Func factory, string? contract = null) + where TService : class + { + ArgumentExceptionHelper.ThrowIfNull(factory); + if (contract is null) + { + _resolver.RegisterLazySingleton(factory); + } + else + { + _resolver.RegisterLazySingleton(factory, contract); + } + } + + /// + public void Register(Func factory, string? contract = null) + where TService : class + { + ArgumentExceptionHelper.ThrowIfNull(factory); + if (contract is null) + { + _resolver.Register(factory); + } + else + { + _resolver.Register(factory, contract); + } + } +} diff --git a/src/ReactiveUI/Registration/Registrations.cs b/src/ReactiveUI/Registration/Registrations.cs index 1651af7e6b..6bd66be76c 100644 --- a/src/ReactiveUI/Registration/Registrations.cs +++ b/src/ReactiveUI/Registration/Registrations.cs @@ -16,34 +16,198 @@ namespace ReactiveUI; public class Registrations : IWantsToRegisterStuff { /// - [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Does not use reflection")] - [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Does not use reflection")] - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - ArgumentExceptionHelper.ThrowIfNull(registerFunction); - - registerFunction(static () => new INPCObservableForProperty(), typeof(ICreatesObservableForProperty)); - registerFunction(static () => new IROObservableForProperty(), typeof(ICreatesObservableForProperty)); - registerFunction(static () => new POCOObservableForProperty(), typeof(ICreatesObservableForProperty)); - registerFunction(static () => new EqualityTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new StringConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new ByteToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableByteToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new ShortToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableShortToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new IntegerToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableIntegerToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new LongToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableLongToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new SingleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableSingleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new DoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableDoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new DecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new NullableDecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(static () => new DefaultViewLocator(), typeof(IViewLocator)); - registerFunction(static () => new CanActivateViewFetcher(), typeof(IActivationForViewFetcher)); - registerFunction(static () => new CreatesCommandBindingViaEvent(), typeof(ICreatesCommandBinding)); - registerFunction(static () => new CreatesCommandBindingViaCommandParameter(), typeof(ICreatesCommandBinding)); + ArgumentExceptionHelper.ThrowIfNull(registrar); + + registrar.RegisterConstant(static () => new INPCObservableForProperty()); + registrar.RegisterConstant(static () => new IROObservableForProperty()); + registrar.RegisterConstant(static () => new POCOObservableForProperty()); + + // General converters - register to Splat for backward compatibility + RegisterConverter(registrar, new EqualityTypeConverter()); + RegisterConverter(registrar, new StringConverter()); + + // Numeric → String converters + RegisterConverter(registrar, new ByteToStringTypeConverter()); + RegisterConverter(registrar, new NullableByteToStringTypeConverter()); + RegisterConverter(registrar, new ShortToStringTypeConverter()); + RegisterConverter(registrar, new NullableShortToStringTypeConverter()); + RegisterConverter(registrar, new IntegerToStringTypeConverter()); + RegisterConverter(registrar, new NullableIntegerToStringTypeConverter()); + RegisterConverter(registrar, new LongToStringTypeConverter()); + RegisterConverter(registrar, new NullableLongToStringTypeConverter()); + RegisterConverter(registrar, new SingleToStringTypeConverter()); + RegisterConverter(registrar, new NullableSingleToStringTypeConverter()); + RegisterConverter(registrar, new DoubleToStringTypeConverter()); + RegisterConverter(registrar, new NullableDoubleToStringTypeConverter()); + RegisterConverter(registrar, new DecimalToStringTypeConverter()); + RegisterConverter(registrar, new NullableDecimalToStringTypeConverter()); + + // String → Numeric converters + RegisterConverter(registrar, new StringToByteTypeConverter()); + RegisterConverter(registrar, new StringToNullableByteTypeConverter()); + RegisterConverter(registrar, new StringToShortTypeConverter()); + RegisterConverter(registrar, new StringToNullableShortTypeConverter()); + RegisterConverter(registrar, new StringToIntegerTypeConverter()); + RegisterConverter(registrar, new StringToNullableIntegerTypeConverter()); + RegisterConverter(registrar, new StringToLongTypeConverter()); + RegisterConverter(registrar, new StringToNullableLongTypeConverter()); + RegisterConverter(registrar, new StringToSingleTypeConverter()); + RegisterConverter(registrar, new StringToNullableSingleTypeConverter()); + RegisterConverter(registrar, new StringToDoubleTypeConverter()); + RegisterConverter(registrar, new StringToNullableDoubleTypeConverter()); + RegisterConverter(registrar, new StringToDecimalTypeConverter()); + RegisterConverter(registrar, new StringToNullableDecimalTypeConverter()); + + // Boolean ↔ String converters + RegisterConverter(registrar, new BooleanToStringTypeConverter()); + RegisterConverter(registrar, new NullableBooleanToStringTypeConverter()); + RegisterConverter(registrar, new StringToBooleanTypeConverter()); + RegisterConverter(registrar, new StringToNullableBooleanTypeConverter()); + + // Guid ↔ String converters + RegisterConverter(registrar, new GuidToStringTypeConverter()); + RegisterConverter(registrar, new NullableGuidToStringTypeConverter()); + RegisterConverter(registrar, new StringToGuidTypeConverter()); + RegisterConverter(registrar, new StringToNullableGuidTypeConverter()); + + // DateTime ↔ String converters + RegisterConverter(registrar, new DateTimeToStringTypeConverter()); + RegisterConverter(registrar, new NullableDateTimeToStringTypeConverter()); + RegisterConverter(registrar, new StringToDateTimeTypeConverter()); + RegisterConverter(registrar, new StringToNullableDateTimeTypeConverter()); + + // DateTimeOffset ↔ String converters + RegisterConverter(registrar, new DateTimeOffsetToStringTypeConverter()); + RegisterConverter(registrar, new NullableDateTimeOffsetToStringTypeConverter()); + RegisterConverter(registrar, new StringToDateTimeOffsetTypeConverter()); + RegisterConverter(registrar, new StringToNullableDateTimeOffsetTypeConverter()); + + // TimeSpan ↔ String converters + RegisterConverter(registrar, new TimeSpanToStringTypeConverter()); + RegisterConverter(registrar, new NullableTimeSpanToStringTypeConverter()); + RegisterConverter(registrar, new StringToTimeSpanTypeConverter()); + RegisterConverter(registrar, new StringToNullableTimeSpanTypeConverter()); + +#if NET6_0_OR_GREATER + // DateOnly ↔ String converters (.NET 6+) + RegisterConverter(registrar, new DateOnlyToStringTypeConverter()); + RegisterConverter(registrar, new NullableDateOnlyToStringTypeConverter()); + RegisterConverter(registrar, new StringToDateOnlyTypeConverter()); + RegisterConverter(registrar, new StringToNullableDateOnlyTypeConverter()); + + // TimeOnly ↔ String converters (.NET 6+) + RegisterConverter(registrar, new TimeOnlyToStringTypeConverter()); + RegisterConverter(registrar, new NullableTimeOnlyToStringTypeConverter()); + RegisterConverter(registrar, new StringToTimeOnlyTypeConverter()); + RegisterConverter(registrar, new StringToNullableTimeOnlyTypeConverter()); +#endif + + // Uri ↔ String converters + RegisterConverter(registrar, new UriToStringTypeConverter()); + RegisterConverter(registrar, new StringToUriTypeConverter()); + + // Nullable ↔ Non-Nullable converters + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + RegisterUnidirectionalConverter(registrar); + + registrar.RegisterConstant(static () => new DefaultViewLocator()); + registrar.RegisterConstant(static () => new CanActivateViewFetcher()); + registrar.RegisterConstant(static () => new CreatesCommandBindingViaEvent()); + registrar.RegisterConstant(static () => new CreatesCommandBindingViaCommandParameter()); + } + + /// + /// Helper method to register a converter to Splat for backward compatibility. + /// + /// The Splat registrar. + /// The converter instance to register. + /// + /// This registers converters to Splat for backward compatibility. + /// When using ReactiveUIBuilder, converters are also registered to the + /// ConverterService separately through the builder's initialization. + /// + private static void RegisterConverter( + IRegistrar registrar, + IBindingTypeConverter converter) + { + ArgumentExceptionHelper.ThrowIfNull(registrar); + ArgumentExceptionHelper.ThrowIfNull(converter); + + // Register to Splat for backward compatibility + registrar.RegisterConstant(() => converter); + } + + /// + /// Helper method to register a unidirectional type converter with explicit generic instantiation. + /// + /// The source type. + /// The target type. + /// The converter type that handles TFrom→TTo conversion. + /// The dependency resolver to register with. + /// + /// This method registers the converter twice: + /// + /// As for typed lookup + /// As for affinity-based discovery + /// + /// + private static void RegisterUnidirectionalConverter( + IRegistrar registrar) + where TConverter : IBindingTypeConverter, new() + { + ArgumentExceptionHelper.ThrowIfNull(registrar); + + var instance = new TConverter(); + + // Register typed interface for direct type lookup + registrar.RegisterConstant>(() => instance); + + // Register base interface for affinity-based discovery + registrar.RegisterConstant(() => instance); + } + + /// + /// Helper method to register a bidirectional type converter with explicit generic instantiations. + /// + /// The source type. + /// The target type. + /// The converter type that handles both TFrom→TTo and TTo→TFrom conversions. + /// The dependency resolver to register with. + /// + /// This method registers the converter three times: + /// + /// As for TFrom→TTo conversion + /// As for TTo→TFrom conversion + /// As for affinity-based discovery + /// + /// + private static void RegisterBidirectionalConverter( + IRegistrar registrar) + where TConverter : IBindingTypeConverter, IBindingTypeConverter, new() + { + ArgumentExceptionHelper.ThrowIfNull(registrar); + + var instance = new TConverter(); + + // Register both generic directions + registrar.Register>(() => instance); + registrar.Register>(() => instance); + + // Register base interface for affinity-based discovery + registrar.RegisterConstant(() => instance); } } diff --git a/src/ReactiveUI/RegistrationNamespace.cs b/src/ReactiveUI/RegistrationNamespace.cs deleted file mode 100644 index d2de0fd911..0000000000 --- a/src/ReactiveUI/RegistrationNamespace.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2025 .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; - -/// -/// Platforms or other registration namespaces for the dependency resolver to consider when initializing. -/// -public enum RegistrationNamespace -{ - /// No platform to register. - None = 0, - - /// - /// Windows Forms. - /// - Winforms, - - /// - /// WPF. - /// - Wpf, - - /// - /// Uno. - /// - Uno, - - /// - /// Uno Win UI. - /// - UnoWinUI, - - /// - /// Blazor. - /// - Blazor, - - /// - /// Drawing. - /// - Drawing, - - /// - /// Avalonia. - /// - Avalonia, - - /// - /// Maui. - /// - Maui, - - /// - /// Uwp. - /// - Uwp, - - /// - /// WinUI. - /// - WinUI, -} diff --git a/src/ReactiveUI/Routing/MessageBus.cs b/src/ReactiveUI/Routing/MessageBus.cs index 10714e21ab..71eb7fc1d7 100644 --- a/src/ReactiveUI/Routing/MessageBus.cs +++ b/src/ReactiveUI/Routing/MessageBus.cs @@ -29,7 +29,7 @@ public class MessageBus : IMessageBus /// /// Gets or sets the Current MessageBus. /// - public static IMessageBus Current { get; set; } = new MessageBus(); // TODO: Create Test + public static IMessageBus Current { get; set; } = new MessageBus(); /// /// Registers a scheduler for the type, which may be specified at runtime, and the contract. @@ -41,7 +41,7 @@ public class MessageBus : IMessageBus /// A unique string to distinguish messages with /// identical types (i.e. "MyCoolViewModel") - if the message type is /// only used for one purpose, leave this as null. - public void RegisterScheduler(IScheduler scheduler, string? contract = null) => // TODO: Create Test + public void RegisterScheduler(IScheduler scheduler, string? contract = null) => _schedulerMappings[(typeof(T), contract)] = scheduler; /// @@ -56,9 +56,11 @@ public void RegisterScheduler(IScheduler scheduler, string? contract = null) /// message bus. public IObservable Listen(string? contract = null) { - this.Log().Info(CultureInfo.InvariantCulture, "Listening to {0}:{1}", typeof(T), contract); - - return SetupSubjectIfNecessary(contract).Skip(1); + return Observable.Defer(() => + { + this.Log().Info(CultureInfo.InvariantCulture, "Listening to {0}:{1}", typeof(T), contract); + return SetupSubjectIfNecessary(contract).Skip(1); + }); } /// @@ -71,11 +73,13 @@ public IObservable Listen(string? contract = null) /// only used for one purpose, leave this as null. /// An Observable representing the notifications posted to the /// message bus. - public IObservable ListenIncludeLatest(string? contract = null) // TODO: Create Test + public IObservable ListenIncludeLatest(string? contract = null) { - this.Log().Info(CultureInfo.InvariantCulture, "Listening to {0}:{1}", typeof(T), contract); - - return SetupSubjectIfNecessary(contract); + return Observable.Defer(() => + { + this.Log().Info(CultureInfo.InvariantCulture, "Listening to {0}:{1}", typeof(T), contract); + return SetupSubjectIfNecessary(contract); + }); } /// @@ -112,7 +116,12 @@ public IDisposable RegisterMessageSource( { ArgumentExceptionHelper.ThrowIfNull(source); - return source.Subscribe(SetupSubjectIfNecessary(contract)); + var subject = SetupSubjectIfNecessary(contract); + + return source.Subscribe( + subject.OnNext, + ex => this.Log().Warn(ex, "MessageBus source for {0}:{1} terminated with an error.", typeof(T), contract), + () => this.Log().Info(CultureInfo.InvariantCulture, "MessageBus source for {0}:{1} completed.", typeof(T), contract)); } /// diff --git a/src/ReactiveUI/Routing/RoutingState.cs b/src/ReactiveUI/Routing/RoutingState.cs index fcc6dd0465..ee6f540e93 100644 --- a/src/ReactiveUI/Routing/RoutingState.cs +++ b/src/ReactiveUI/Routing/RoutingState.cs @@ -53,34 +53,20 @@ namespace ReactiveUI; /// /// [DataContract] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("RoutingState uses RxApp and ReactiveCommand which require dynamic code generation")] -[RequiresUnreferencedCode("RoutingState uses RxApp and ReactiveCommand which may require unreferenced code")] -#endif public class RoutingState : ReactiveObject { [IgnoreDataMember] [JsonIgnore] private readonly IScheduler _scheduler; - /// - /// Initializes static members of the class. - /// - static RoutingState() => RxApp.EnsureInitialized(); - /// /// Initializes a new instance of the class. /// /// A scheduler for where to send navigation changes to. #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutingState uses ReactiveCommand which requires dynamic code generation.")] - [RequiresUnreferencedCode("RoutingState uses ReactiveCommand which may require unreferenced code.")] -#endif public RoutingState(IScheduler? scheduler = null) -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { - _scheduler = scheduler ?? RxApp.MainThreadScheduler; + _scheduler = scheduler ?? RxSchedulers.MainThreadScheduler; NavigationStack = []; SetupRx(); } @@ -129,19 +115,20 @@ public RoutingState(IScheduler? scheduler = null) [JsonIgnore] public IObservable> NavigationChanged { get; protected set; } + /// + /// Sets up reactive commands and observables after deserialization. + /// + /// The streaming context for deserialization. [OnDeserialized] -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutingState uses ReactiveCommand which requires dynamic code generation.")] [RequiresUnreferencedCode("RoutingState uses ReactiveCommand which may require unreferenced code.")] + [MemberNotNull(nameof(NavigationChanged), nameof(NavigateBack), nameof(Navigate), nameof(NavigateAndReset), nameof(CurrentViewModel))] +#if NET6_0_OR_GREATER private void SetupRx(in StreamingContext sc) => SetupRx(); #else private void SetupRx(StreamingContext sc) => SetupRx(); #endif -#if NET6_0_OR_GREATER - [RequiresDynamicCode("RoutingState uses ReactiveCommand which requires dynamic code generation.")] - [RequiresUnreferencedCode("RoutingState uses ReactiveCommand which may require unreferenced code.")] -#endif + [MemberNotNull(nameof(NavigationChanged), nameof(NavigateBack), nameof(Navigate), nameof(NavigateAndReset), nameof(CurrentViewModel))] private void SetupRx() { var navigateScheduler = _scheduler; diff --git a/src/ReactiveUI/RxApp.cs b/src/ReactiveUI/RxApp.cs deleted file mode 100644 index 78db95496f..0000000000 --- a/src/ReactiveUI/RxApp.cs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) 2025 .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.Runtime.CompilerServices; - -using Splat.Builder; - -namespace ReactiveUI; - -/// -/// The main registration point for common class instances throughout a ReactiveUI application. -/// -/// -/// N.B. Why we have this evil global class -/// In a WPF or UWP application, most commands must have the Dispatcher -/// scheduler set, because notifications will end up being run on another thread; -/// this happens most often in a CanExecute observable.Unfortunately, in a Unit -/// Test framework, while the MS Test Unit runner will *set* the Dispatcher (so -/// we can't even use the lack of its presence to determine whether we're in a -/// test runner or not), none of the items queued to it will ever be executed -/// during the unit test. -/// Initially, I tried to plumb the ability to set the scheduler throughout the -/// classes, but when you start building applications on top of that, having to -/// have *every single * class have a default Scheduler property is really -/// irritating, with either default making life difficult. -/// This class also initializes a whole bunch of other stuff, including the IoC container, -/// logging and error handling. -/// -public static class RxApp -{ -#if ANDROID || IOS - /// - /// The size of a small cache of items. Often used for the MemoizingMRUCache class. - /// - public const int SmallCacheLimit = 32; - - /// - /// The size of a large cache of items. Often used for the MemoizingMRUCache class. - /// - public const int BigCacheLimit = 64; -#else - /// - /// The size of a small cache of items. Often used for the MemoizingMRUCache class. - /// - public const int SmallCacheLimit = 64; - - /// - /// The size of a large cache of items. Often used for the MemoizingMRUCache class. - /// - public const int BigCacheLimit = 256; -#endif - - [ThreadStatic] - [SuppressMessage("Reliability", "CA2019:Improper 'ThreadStatic' field initialization", Justification = "By Design")] - private static IScheduler _unitTestTaskpoolScheduler = null!; - - [ThreadStatic] - [SuppressMessage("Reliability", "CA2019:Improper 'ThreadStatic' field initialization", Justification = "By Design")] - private static IScheduler _unitTestMainThreadScheduler = null!; - - [ThreadStatic] - [SuppressMessage("Reliability", "CA2019:Improper 'ThreadStatic' field initialization", Justification = "By Design")] - private static ISuspensionHost _unitTestSuspensionHost = null!; - - private static ISuspensionHost _suspensionHost = null!; - private static bool _hasSchedulerBeenChecked; - - /// - /// Initializes static members of the class. - /// - /// Default exception when we have unhandled exception in RxUI. - [SuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Recommend using AppBuilder")] - [SuppressMessage("Trimming", "IL2026:Calling members annotated with 'RequiresUnreferencedCodeAttribute' may break functionality when trimming.", Justification = "Recommend using AppBuilder")] - static RxApp() - { -#if !PORTABLE - RxSchedulers.TaskpoolScheduler = TaskPoolScheduler.Default; -#endif - AppLocator.CurrentMutable.InitializeSplat(); - - if (!AppBuilder.UsingBuilder) - { - AppLocator.RegisterResolverCallbackChanged(() => - { - if (AppLocator.CurrentMutable is null) - { - return; - } - - AppLocator.CurrentMutable.InitializeSplat(); - AppLocator.CurrentMutable.InitializeReactiveUI(PlatformRegistrationManager.NamespacesToRegister); - }); - } - - DefaultExceptionHandler = Observer.Create(ex => - { - // NB: If you're seeing this, it means that an - // ObservableAsPropertyHelper or the CanExecute of a - // ReactiveCommand ended in an OnError. Instead of silently - // breaking, ReactiveUI will halt here if a debugger is attached. - if (Debugger.IsAttached) - { - Debugger.Break(); - } - -#pragma warning disable CA1065 // Avoid exceptions in constructors -- In scheduler. - 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.", - ex)); -#pragma warning restore CA1065 - }); - - _suspensionHost = new SuspensionHost(); - if (ModeDetector.InUnitTestRunner()) - { - LogHost.Default.Warn("*** Detected Unit Test Runner, setting MainThreadScheduler to CurrentThread ***"); - LogHost.Default.Warn("If we are not actually in a test runner, please file a bug\n\n"); - LogHost.Default.Warn("ReactiveUI acts differently under a test runner, see the docs\n"); - LogHost.Default.Warn("for more info about what to expect"); - - UnitTestMainThreadScheduler = CurrentThreadScheduler.Instance; - return; - } - - LogHost.Default.Info("Initializing to normal mode"); - - RxSchedulers.MainThreadScheduler ??= DefaultScheduler.Instance; - } - - /// - /// Gets or sets a scheduler used to schedule work items that - /// should be run "on the UI thread". In normal mode, this will be - /// DispatcherScheduler, and in Unit Test mode this will be Immediate, - /// to simplify writing common unit tests. - /// - /// - /// Consider using RxSchedulers.MainThreadScheduler for new code to avoid RequiresUnreferencedCode attributes. - /// - public static IScheduler MainThreadScheduler - { - get - { - if (ModeDetector.InUnitTestRunner()) - { - return UnitTestMainThreadScheduler; - } - - // If Scheduler is DefaultScheduler, user is likely using .NET Standard - if (!_hasSchedulerBeenChecked && RxSchedulers.MainThreadScheduler == Scheduler.Default) - { - _hasSchedulerBeenChecked = true; - LogHost.Default.Warn("It seems you are running .NET Standard, but there is no host package installed!\n"); - LogHost.Default.Warn("You will need to install the specific host package for your platform (ReactiveUI.WPF, ReactiveUI.Blazor, ...)\n"); - LogHost.Default.Warn("You can install the needed package via NuGet, see https://reactiveui.net/docs/getting-started/installation/"); - } - - return RxSchedulers.MainThreadScheduler!; - } - - set - { - // N.B. The ThreadStatic dance here is for the unit test case - - // often, each test will override MainThreadScheduler with their - // own TestScheduler, and if this wasn't ThreadStatic, they would - // stomp on each other, causing test cases to randomly fail, - // then pass when you rerun them. - if (ModeDetector.InUnitTestRunner()) - { - UnitTestMainThreadScheduler = value; - RxSchedulers.MainThreadScheduler ??= value; - } - else - { - RxSchedulers.MainThreadScheduler = value; - } - } - } - - /// - /// Gets or sets the a the scheduler used to schedule work items to - /// run in a background thread. In both modes, this will run on the TPL - /// Task Pool. - /// - /// - /// Consider using RxSchedulers.TaskpoolScheduler for new code to avoid RequiresUnreferencedCode attributes. - /// - public static IScheduler TaskpoolScheduler - { - get => _unitTestTaskpoolScheduler ?? RxSchedulers.TaskpoolScheduler; - set - { - if (ModeDetector.InUnitTestRunner()) - { - _unitTestTaskpoolScheduler = value; - RxSchedulers.TaskpoolScheduler ??= value; - } - else - { - RxSchedulers.TaskpoolScheduler = value; - } - } - } - - /// - /// Gets or sets a value indicating whether log messages should be suppressed for command bindings in the view. - /// - public static bool SuppressViewCommandBindingMessage { get; set; } - - /// - /// Gets or sets the Observer which signaled whenever an object that has a - /// ThrownExceptions property doesn't Subscribe to that Observable. Use - /// Observer.Create to set up what will happen - the default is to crash - /// the application with an error message. - /// - public static IObserver DefaultExceptionHandler { get; set; } = null!; - - /// - /// Gets or sets the current SuspensionHost, a - /// class which provides events for process lifetime events, especially - /// on mobile devices. - /// - public static ISuspensionHost SuspensionHost - { - get => _unitTestSuspensionHost ?? _suspensionHost; - set - { - if (ModeDetector.InUnitTestRunner()) - { - _unitTestSuspensionHost = value; - _suspensionHost ??= value; - } - else - { - _suspensionHost = value; - } - } - } - - internal static IScheduler UnitTestMainThreadScheduler - { - get => _unitTestMainThreadScheduler ??= CurrentThreadScheduler.Instance; - - set => _unitTestMainThreadScheduler = value; - } - - /// - /// Set up default initializations. - /// - [MethodImpl(MethodImplOptions.NoOptimization)] - internal static void EnsureInitialized() - { - // NB: This method only exists to invoke the static constructor - } -} diff --git a/src/ReactiveUI/RxCacheSize.cs b/src/ReactiveUI/RxCacheSize.cs new file mode 100644 index 0000000000..1a8a8bd97d --- /dev/null +++ b/src/ReactiveUI/RxCacheSize.cs @@ -0,0 +1,90 @@ +// Copyright (c) 2025 .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 configurable cache size limits for ReactiveUI's internal caching mechanisms. +/// These values can be configured via or will auto-initialize with platform-specific defaults. +/// +public static class RxCacheSize +{ +#if ANDROID || IOS + private const int DefaultSmallCacheLimit = 32; + private const int DefaultBigCacheLimit = 64; +#else + private const int DefaultSmallCacheLimit = 64; + private const int DefaultBigCacheLimit = 256; +#endif + + private static int _smallCacheLimit; + private static int _bigCacheLimit; + private static int _initialized; // 0 = false, 1 = true + + /// + /// Gets the small cache limit used for internal memoizing caches. + /// Default: 32 (mobile platforms) or 64 (desktop platforms). + /// + public static int SmallCacheLimit + { + get + { + EnsureInitialized(); + return _smallCacheLimit; + } + } + + /// + /// Gets the big cache limit used for internal memoizing caches. + /// Default: 64 (mobile platforms) or 256 (desktop platforms). + /// + public static int BigCacheLimit + { + get + { + EnsureInitialized(); + return _bigCacheLimit; + } + } + + /// + /// Initializes the cache size limits. Called by ReactiveUIBuilder. + /// + /// The small cache limit to use. + /// The big cache limit to use. + internal static void Initialize(int smallCacheLimit, int bigCacheLimit) + { + if (Interlocked.CompareExchange(ref _initialized, 1, 0) == 0) + { + _smallCacheLimit = smallCacheLimit; + _bigCacheLimit = bigCacheLimit; + } + } + + /// + /// Resets the cache size state for testing purposes. + /// + /// + /// WARNING: This method should ONLY be used in unit tests to reset state between test runs. + /// Never call this in production code as it can lead to inconsistent application state. + /// + internal static void ResetForTesting() + { + Interlocked.Exchange(ref _initialized, 0); + _smallCacheLimit = 0; + _bigCacheLimit = 0; + } + + /// + /// Ensures cache sizes are initialized with platform defaults if not already configured. + /// + private static void EnsureInitialized() + { + if (Interlocked.CompareExchange(ref _initialized, 0, 0) == 0) + { + Initialize(DefaultSmallCacheLimit, DefaultBigCacheLimit); + } + } +} diff --git a/src/ReactiveUI/RxSchedulers.cs b/src/ReactiveUI/RxSchedulers.cs index ac08d4105f..7e1c6812f6 100644 --- a/src/ReactiveUI/RxSchedulers.cs +++ b/src/ReactiveUI/RxSchedulers.cs @@ -8,32 +8,36 @@ namespace ReactiveUI; /// -/// Provides access to ReactiveUI schedulers without requiring unreferenced code attributes. -/// This is a lightweight alternative to RxApp for consuming scheduler properties. +/// Provides access to ReactiveUI schedulers. /// /// -/// This class provides basic scheduler functionality without the overhead of dependency injection -/// or unit test detection, allowing consumers to access schedulers without needing -/// RequiresUnreferencedCode attributes. For full functionality including unit test support, -/// use RxApp schedulers instead. +/// This class provides scheduler functionality without requiring unreferenced code attributes, +/// making it suitable for AOT compilation scenarios. RxApp scheduler properties delegate to +/// this class and add builder initialization checks. /// public static class RxSchedulers { +#if NET9_0_OR_GREATER + private static readonly Lock _lock = new(); +#else private static readonly object _lock = new(); +#endif private static volatile IScheduler? _mainThreadScheduler; private static volatile IScheduler? _taskpoolScheduler; + static RxSchedulers() + { + TaskpoolScheduler = TaskPoolScheduler.Default; + MainThreadScheduler ??= DefaultScheduler.Instance; + } + /// /// Gets or sets a scheduler used to schedule work items that /// should be run "on the UI thread". In normal mode, this will be /// DispatcherScheduler. This defaults to DefaultScheduler.Instance. /// - /// - /// This is a simplified version that doesn't include unit test detection. - /// For full functionality including unit test support, use RxSchedulers.MainThreadScheduler. - /// public static IScheduler MainThreadScheduler { get @@ -62,10 +66,6 @@ public static IScheduler MainThreadScheduler /// Gets or sets the scheduler used to schedule work items to /// run in a background thread. This defaults to TaskPoolScheduler.Default. /// - /// - /// This is a simplified version that doesn't include unit test detection. - /// For full functionality including unit test support, use RxApp.TaskpoolScheduler. - /// public static IScheduler TaskpoolScheduler { get @@ -100,10 +100,40 @@ public static IScheduler TaskpoolScheduler } /// - /// Set up default initializations. + /// Gets or sets a value indicating whether log messages should be suppressed for command bindings in the view. + /// Platform registrations may set this to true to reduce logging noise. + /// + public static bool SuppressViewCommandBindingMessage { get; set; } + + /// + /// Resets the schedulers back to their default values. + /// This method is intended for testing purposes only. + /// + /// + /// WARNING: This method should ONLY be used in unit tests to reset state between test runs. + /// Never call this in production code as it can lead to inconsistent application state. + /// Resets: + /// - MainThreadScheduler to DefaultScheduler.Instance. + /// - TaskpoolScheduler to TaskPoolScheduler.Default. + /// + internal static void ResetForTesting() + { + lock (_lock) + { + _mainThreadScheduler = DefaultScheduler.Instance; +#if !PORTABLE + _taskpoolScheduler = TaskPoolScheduler.Default; +#else + _taskpoolScheduler = DefaultScheduler.Instance; +#endif + } + } + + /// + /// Set up default initializations for static constructor. /// [MethodImpl(MethodImplOptions.NoOptimization)] - internal static void EnsureInitialized() + internal static void EnsureStaticConstructorRun() { // NB: This method only exists to invoke the static constructor if needed } diff --git a/src/ReactiveUI/RxState.cs b/src/ReactiveUI/RxState.cs new file mode 100644 index 0000000000..3a48f87382 --- /dev/null +++ b/src/ReactiveUI/RxState.cs @@ -0,0 +1,87 @@ +// Copyright (c) 2025 .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; + +namespace ReactiveUI; + +/// +/// Static class that holds the default exception handler for ReactiveUI. +/// +public static class RxState +{ + private static IObserver? _defaultExceptionHandler; + private static int _exceptionHandlerInitialized; // 0 = false, 1 = true + + /// + /// Gets the default exception handler for unhandled errors in ReactiveUI observables. + /// Auto-initializes with debugger break + UnhandledErrorException if not configured via builder. + /// + public static IObserver DefaultExceptionHandler + { + get + { + if (Interlocked.CompareExchange(ref _exceptionHandlerInitialized, 0, 0) == 0) + { + InitializeDefaultExceptionHandler(); + } + + return _defaultExceptionHandler!; + } + } + + /// + /// Initializes the exception handler with a custom observer. Called by ReactiveUIBuilder. + /// + /// The custom exception handler to use. + internal static void InitializeExceptionHandler(IObserver exceptionHandler) + { + if (Interlocked.CompareExchange(ref _exceptionHandlerInitialized, 1, 0) == 0) + { + _defaultExceptionHandler = exceptionHandler ?? throw new ArgumentNullException(nameof(exceptionHandler)); + } + } + + /// + /// Resets the exception handler state for testing purposes. + /// + /// + /// WARNING: This method should ONLY be used in unit tests to reset state between test runs. + /// Never call this in production code as it can lead to inconsistent application state. + /// + internal static void ResetForTesting() + { + Interlocked.Exchange(ref _exceptionHandlerInitialized, 0); + _defaultExceptionHandler = null; + } + + /// + /// Initializes the default exception handler if not already configured. + /// Creates an observer that breaks debugger and throws UnhandledErrorException. + /// + private static void InitializeDefaultExceptionHandler() + { + if (Interlocked.CompareExchange(ref _exceptionHandlerInitialized, 1, 0) == 0) + { + _defaultExceptionHandler = Observer.Create(ex => + { + // NB: If you're seeing this, it means that an + // ObservableAsPropertyHelper or the CanExecute of a + // ReactiveCommand ended in an OnError. Instead of silently + // breaking, ReactiveUI will halt here if a debugger is attached. + if (Debugger.IsAttached) + { + Debugger.Break(); + } + +#pragma warning disable CA1065 // Avoid exceptions in constructors -- In scheduler. + 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.", + ex)); +#pragma warning restore CA1065 + }); + } + } +} diff --git a/src/ReactiveUI/RxSuspension.cs b/src/ReactiveUI/RxSuspension.cs new file mode 100644 index 0000000000..2e4a5e91bf --- /dev/null +++ b/src/ReactiveUI/RxSuspension.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2025 .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 access to ReactiveUI suspension functionality. +/// +/// +/// This class provides suspension host functionality for application lifecycle management. +/// Configure using or +/// . +/// +public static class RxSuspension +{ + private static ISuspensionHost? _suspensionHost; + private static int _suspensionHostInitialized; // 0 = false, 1 = true + + /// + /// Gets the suspension host for application lifecycle management. + /// Provides events for process lifetime events, especially on mobile devices. + /// Auto-initializes with default SuspensionHost if not configured via builder. + /// + public static ISuspensionHost SuspensionHost + { + get + { + if (Interlocked.CompareExchange(ref _suspensionHostInitialized, 0, 0) == 0) + { + InitializeDefaultSuspensionHost(); + } + + return _suspensionHost!; + } + } + + /// + /// Initializes the suspension host with a custom instance. Called by ReactiveUIBuilder. + /// + /// The custom suspension host to use. + internal static void InitializeSuspensionHost(ISuspensionHost suspensionHost) + { + if (Interlocked.CompareExchange(ref _suspensionHostInitialized, 1, 0) == 0) + { + _suspensionHost = suspensionHost ?? throw new ArgumentNullException(nameof(suspensionHost)); + } + } + + /// + /// Resets the suspension host state for testing purposes. + /// + /// + /// WARNING: This method should ONLY be used in unit tests to reset state between test runs. + /// Never call this in production code as it can lead to inconsistent application state. + /// + internal static void ResetForTesting() + { + Interlocked.Exchange(ref _suspensionHostInitialized, 0); + _suspensionHost = null; + } + + /// + /// Initializes the default suspension host if not already configured. + /// Creates a new SuspensionHost instance. + /// + private static void InitializeDefaultSuspensionHost() + { + if (Interlocked.CompareExchange(ref _suspensionHostInitialized, 1, 0) == 0) + { + _suspensionHost = new SuspensionHost(); + } + } +} diff --git a/src/ReactiveUI/Suspension/DummySuspensionDriver.cs b/src/ReactiveUI/Suspension/DummySuspensionDriver.cs index 4cf9a382b4..5788a4fb03 100644 --- a/src/ReactiveUI/Suspension/DummySuspensionDriver.cs +++ b/src/ReactiveUI/Suspension/DummySuspensionDriver.cs @@ -3,36 +3,58 @@ // 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; +using System.Text.Json.Serialization.Metadata; + namespace ReactiveUI; /// -/// A suspension driver that does not do anything. -/// Useful potentially for unit testing or for platforms -/// where you don't want to use a Suspension Driver. +/// A suspension driver that does not persist any state. /// -public class DummySuspensionDriver : ISuspensionDriver +/// +/// +/// This driver is useful for unit tests and for platforms or applications where persistence is undesired. +/// +/// +/// All load operations return (or for the requested type), +/// and all save/invalidate operations complete immediately. +/// +/// +public sealed class DummySuspensionDriver : ISuspensionDriver { - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("LoadState uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("LoadState uses methods that may require unreferenced code")] -#endif - public IObservable LoadState() => // TODO: Create Test - Observable.Default; + /// + [RequiresUnreferencedCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer LoadState(JsonTypeInfo) for trimming or AOT scenarios.")] + [RequiresDynamicCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer LoadState(JsonTypeInfo) for trimming or AOT scenarios.")] + public IObservable LoadState() + => Observable.Return((object?)null); + + /// + [RequiresUnreferencedCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer SaveState(T, JsonTypeInfo) for trimming or AOT scenarios.")] + [RequiresDynamicCode( + "Implementations commonly use reflection-based serialization. " + + "Prefer SaveState(T, JsonTypeInfo) for trimming or AOT scenarios.")] + public IObservable SaveState(T state) + => Observables.Unit; + + /// + public IObservable LoadState(JsonTypeInfo typeInfo) + { + return Observable.Return(default); + } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SaveState uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("SaveState uses methods that may require unreferenced code")] -#endif - public IObservable SaveState(object state) => // TODO: Create Test - Observables.Unit; + /// + public IObservable SaveState(T state, JsonTypeInfo typeInfo) + { + return Observables.Unit; + } - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("InvalidateState uses methods that require dynamic code generation")] - [RequiresUnreferencedCode("InvalidateState uses methods that may require unreferenced code")] -#endif - public IObservable InvalidateState() => // TODO: Create Test - Observables.Unit; + /// + public IObservable InvalidateState() + => Observables.Unit; } diff --git a/src/ReactiveUI/Suspension/SuspensionHost.cs b/src/ReactiveUI/Suspension/SuspensionHost.cs index 92e1fb451d..f2ee14b22b 100644 --- a/src/ReactiveUI/Suspension/SuspensionHost.cs +++ b/src/ReactiveUI/Suspension/SuspensionHost.cs @@ -3,6 +3,8 @@ // 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.Interfaces; + namespace ReactiveUI; /// @@ -11,12 +13,12 @@ namespace ReactiveUI; /// /// /// -/// backs and provides concrete observables that are wired up +/// backs and provides concrete observables that are wired up /// by helpers such as AutoSuspendHelper. Platform hosts push their lifecycle notifications into the /// instances exposed here and view models subscribe through . /// /// -/// Consumers rarely instantiate this type directly; instead call to access the singleton. The +/// Consumers rarely instantiate this type directly; instead call to access the singleton. The /// object is intentionally thread-safe via so events raised prior to subscription are /// replayed to late subscribers. /// @@ -143,7 +145,7 @@ public SuspensionHost() /// /// /// The value should be a serializable aggregate that represents the shell of your application. It is populated via - /// during resume and saved through + /// during resume and saved through the SuspensionDriver's SaveState /// when fires. /// public object? AppState diff --git a/src/ReactiveUI/Suspension/SuspensionHostExtensions.cs b/src/ReactiveUI/Suspension/SuspensionHostExtensions.cs index f71921f5de..cd71376aff 100644 --- a/src/ReactiveUI/Suspension/SuspensionHostExtensions.cs +++ b/src/ReactiveUI/Suspension/SuspensionHostExtensions.cs @@ -3,7 +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; using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization.Metadata; + +using ReactiveUI.Interfaces; namespace ReactiveUI; @@ -59,6 +63,12 @@ internal static ISuspensionDriver? SuspensionDriver /// Calling this method triggers a one-time load via if the state has not /// yet been materialized, ensuring late subscribers still receive persisted data. /// + [RequiresUnreferencedCode( + "This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. " + + "Prefer GetAppState(ISuspensionHost) used with SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] + [RequiresDynamicCode( + "This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. " + + "Prefer GetAppState(ISuspensionHost) used with SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] public static T GetAppState(this ISuspensionHost item) { ArgumentExceptionHelper.ThrowIfNull(item); @@ -68,6 +78,26 @@ public static T GetAppState(this ISuspensionHost item) return (T)item.AppState!; } + /// + /// Gets the current strongly-typed application state. + /// + /// The app state type. + /// The typed suspension host. + /// The app state. + /// + /// Calling this method triggers a one-time load if the state has not yet been materialized. + /// For trimming/AOT-safe persistence, use . + /// + public static TAppState GetAppState(this ISuspensionHost item) + where TAppState : class + { + ArgumentExceptionHelper.ThrowIfNull(item); + + Interlocked.Exchange(ref _ensureLoadAppStateFunc, null)?.Invoke(); + + return item.AppStateValue!; + } + /// /// Observe changes to the AppState of a class derived from ISuspensionHost. /// @@ -78,10 +108,12 @@ public static T GetAppState(this ISuspensionHost item) /// Emits the current value immediately (if available) and every subsequent assignment so downstream components can /// react to hot reloads or state restoration. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("ObserveAppState uses WhenAny which requires dynamic code generation for expression tree analysis")] - [RequiresUnreferencedCode("ObserveAppState uses WhenAny which may reference members that could be trimmed")] -#endif + [RequiresUnreferencedCode( + "This overload uses WhenAny, which can require unreferenced/dynamic code in trimming/AOT scenarios. " + + "Prefer ObserveAppState(ISuspensionHost) for trimming/AOT scenarios.")] + [RequiresDynamicCode( + "This overload uses WhenAny, which can require unreferenced/dynamic code in trimming/AOT scenarios. " + + "Prefer ObserveAppState(ISuspensionHost) for trimming/AOT scenarios.")] public static IObservable ObserveAppState(this ISuspensionHost item) where T : class { @@ -92,6 +124,35 @@ public static IObservable ObserveAppState(this ISuspensionHost item) .Cast(); } + /// + /// Observes changes to the typed application state without using WhenAny APIs (trimming/AOT friendly). + /// + /// The application state type. + /// The typed suspension host. + /// An observable of the typed application state. + /// + /// Emits the current value immediately (if available) and every subsequent assignment to . + /// + public static IObservable ObserveAppState(this ISuspensionHost item) + where TAppState : class + { + ArgumentExceptionHelper.ThrowIfNull(item); + + return Observable.Create( + observer => + { + var current = item.AppStateValue; + if (current is not null) + { + observer.OnNext(current); + } + + return item.AppStateValueChanged + .WhereNotNull() + .Subscribe(observer); + }); + } + /// /// Setup our suspension driver for a class derived off ISuspensionHost interface. /// This will make your suspension host respond to suspend and resume requests. @@ -109,15 +170,17 @@ public static IObservable ObserveAppState(this ISuspensionHost item) /// /// /// new ShellState(); - /// RxApp.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(FileSystem.AppDataDirectory)); + /// RxSuspension.SuspensionHost.CreateNewAppState = () => new ShellState(); + /// RxSuspension.SuspensionHost.SetupDefaultSuspendResume(new FileSuspensionDriver(FileSystem.AppDataDirectory)); /// ]]> /// /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("SetupDefaultSuspendResume uses ISuspensionDriver which may require dynamic code generation for serialization")] - [RequiresUnreferencedCode("SetupDefaultSuspendResume uses ISuspensionDriver which may require unreferenced code for serialization")] -#endif + [RequiresUnreferencedCode( + "This overload may invoke ISuspensionDriver.LoadState()/SaveState(T), which are commonly reflection-based. " + + "Prefer SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] + [RequiresDynamicCode( + "This overload may invoke ISuspensionDriver.LoadState()/SaveState(T), which are commonly reflection-based. " + + "Prefer SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] public static IDisposable SetupDefaultSuspendResume(this ISuspensionHost item, ISuspensionDriver? driver = null) { ArgumentExceptionHelper.ThrowIfNull(item); @@ -150,16 +213,64 @@ public static IDisposable SetupDefaultSuspendResume(this ISuspensionHost item, I return ret; } + /// + /// Sets up suspend/resume using a strongly-typed host and source-generated JSON metadata (trimming/AOT friendly). + /// + /// The application state type. + /// The typed suspension host. + /// Source-generated metadata for . + /// The suspension driver. + /// A disposable which will stop responding to Suspend and Resume requests. + /// + /// This overload persists and restores state using and + /// to avoid reflection-based serialization. + /// + public static IDisposable SetupDefaultSuspendResume(this ISuspensionHost item, JsonTypeInfo typeInfo, ISuspensionDriver? driver = null) + where TAppState : class + { + ArgumentExceptionHelper.ThrowIfNull(item); + ArgumentExceptionHelper.ThrowIfNull(typeInfo); + + var ret = new CompositeDisposable(); + _suspensionDriver ??= driver ?? AppLocator.Current.GetService(); + + if (_suspensionDriver is null) + { + item.Log().Error("Could not find a valid driver and therefore cannot setup Suspend/Resume."); + return Disposable.Empty; + } + + _ensureLoadAppStateFunc = () => EnsureLoadAppState(item, _suspensionDriver, typeInfo); + + ret.Add(item.ShouldInvalidateState + .SelectMany(_ => _suspensionDriver.InvalidateState()) + .LoggedCatch(item, Observables.Unit, "Tried to invalidate app state") + .Subscribe(_ => item.Log().Info("Invalidated app state"))); + + ret.Add(item.ShouldPersistState + .SelectMany(x => _suspensionDriver.SaveState(item.AppStateValue!, typeInfo).Finally(x.Dispose)) + .LoggedCatch(item, Observables.Unit, "Tried to persist app state") + .Subscribe(_ => item.Log().Info("Persisted application state"))); + + ret.Add(item.IsResuming.Merge(item.IsLaunchingNew) + .Do(_ => Interlocked.Exchange(ref _ensureLoadAppStateFunc, null)?.Invoke()) + .Subscribe()); + + return ret; + } + /// /// Ensures one time app state load from storage. /// /// The suspension host. /// The suspension driver. /// A completed observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("EnsureLoadAppState uses ISuspensionDriver.LoadState which may require dynamic code generation for serialization")] - [RequiresUnreferencedCode("EnsureLoadAppState uses ISuspensionDriver.LoadState which may require unreferenced code for serialization")] -#endif + [RequiresUnreferencedCode( + "This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. " + + "Prefer EnsureLoadAppState(ISuspensionHost, ISuspensionDriver?, JsonTypeInfo) for trimming/AOT scenarios.")] + [RequiresDynamicCode( + "This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. " + + "Prefer EnsureLoadAppState(ISuspensionHost, ISuspensionDriver?, JsonTypeInfo) for trimming/AOT scenarios.")] private static IObservable EnsureLoadAppState(this ISuspensionHost item, ISuspensionDriver? driver = null) { if (item.AppState is not null) @@ -187,4 +298,41 @@ private static IObservable EnsureLoadAppState(this ISuspensionHost item, I return Observable.Return(Unit.Default); } + + /// + /// Ensures a one-time typed app state load from storage using source-generated JSON metadata (trimming/AOT friendly). + /// + /// The application state type. + /// The typed suspension host. + /// The suspension driver. + /// Source-generated metadata for . + /// A completed observable. + private static IObservable EnsureLoadAppState(this ISuspensionHost item, ISuspensionDriver? driver, JsonTypeInfo typeInfo) + where TAppState : class + { + if (item.AppStateValue is not null) + { + return Observable.Return(Unit.Default); + } + + _suspensionDriver ??= driver ?? AppLocator.Current.GetService(); + + if (_suspensionDriver is null) + { + item.Log().Error("Could not find a valid driver and therefore cannot load app state."); + return Observable.Return(Unit.Default); + } + + try + { + item.AppStateValue = _suspensionDriver.LoadState(typeInfo).Wait(); + } + catch (Exception ex) + { + item.Log().Warn(ex, "Failed to restore app state from storage, creating from scratch"); + item.AppStateValue = item.CreateNewAppStateTyped?.Invoke(); + } + + return Observable.Return(Unit.Default); + } } diff --git a/src/ReactiveUI/Suspension/SuspensionHost{TAppState}.cs b/src/ReactiveUI/Suspension/SuspensionHost{TAppState}.cs new file mode 100644 index 0000000000..50242e5dd8 --- /dev/null +++ b/src/ReactiveUI/Suspension/SuspensionHost{TAppState}.cs @@ -0,0 +1,338 @@ +// Copyright (c) 2025 .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.Interfaces; + +namespace ReactiveUI; + +/// +/// Default strongly-typed implementation of . +/// +/// The application state type. +/// +/// +/// This implementation provides settable lifecycle observables and a strongly-typed . +/// +/// +/// The legacy members and +/// are implemented explicitly to preserve compatibility with +/// existing infrastructure. The explicit implementations project to/from the typed properties. +/// +/// +/// Type safety: if a consumer sets to a value not assignable to +/// , the implementation throws an . +/// +/// +public class SuspensionHost : ReactiveObject, ISuspensionHost, IDisposable +{ + /// + /// Holds the observable that signals when the application is launching new. + /// + private readonly ReplaySubject> _isLaunchingNew = new(1); + + /// + /// Holds the observable that signals when the application is resuming from a suspended state. + /// + private readonly ReplaySubject> _isResuming = new(1); + + /// + /// Holds the observable that signals when the application is activated / unpausing. + /// + private readonly ReplaySubject> _isUnpausing = new(1); + + /// + /// Holds the observable that signals when the application should persist its state. + /// + private readonly ReplaySubject> _shouldPersistState = new(1); + + /// + /// Holds the observable that signals when persisted state should be invalidated. + /// + private readonly ReplaySubject> _shouldInvalidateState = new(1); + + /// + /// Holds the observable that signals when the application is continuing from a temporarily paused state. + /// + private readonly ReplaySubject> _isContinuing = new(1); + + /// + /// Publishes changes to when assigned. + /// + private readonly Subject _appStateValueChanged = new(); + + /// + /// Stores the typed application state factory. + /// + private Func? _createNewAppStateTyped; + + /// + /// Stores the current typed application state. + /// + private TAppState? _appState; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The default values throw to indicate that platform-specific suspend/resume wiring has not been installed. + /// Hosts should use AutoSuspendHelper (or an equivalent) to replace these streams. + /// + public SuspensionHost() + { +#if COCOA + const string? message = "Your AppDelegate class needs to use AutoSuspendHelper"; +#elif ANDROID + const string? message = "You need to create an App class and use AutoSuspendHelper"; +#else + const string? message = "Your App class needs to use AutoSuspendHelper"; +#endif + + IsLaunchingNew = IsResuming = IsUnpausing = IsContinuing = ShouldInvalidateState = + Observable.Throw(new Exception(message)); + + ShouldPersistState = Observable.Throw(new Exception(message)); + } + + /// + /// Gets or sets the observable which signals when the application is launching new. + /// + /// + /// Emits when the platform indicates a clean launch (for example, no saved state is available). + /// + /// Thrown when the value is . + public IObservable IsLaunchingNew + { + get => _isLaunchingNew.Switch(); + set + { + ArgumentExceptionHelper.ThrowIfNull(value); + _isLaunchingNew.OnNext(value); + } + } + + /// + /// Gets or sets the observable which signals when the application is resuming from a suspended state. + /// + /// + /// Raised when the host platform reports that the previous process image is being restored. + /// + /// Thrown when the value is . + public IObservable IsResuming + { + get => _isResuming.Switch(); + set + { + ArgumentExceptionHelper.ThrowIfNull(value); + _isResuming.OnNext(value); + } + } + + /// + /// Gets or sets the observable which signals when the application is activated / unpausing. + /// + /// + /// Fired when the app returns to the foreground without being recreated. + /// + /// Thrown when the value is . + public IObservable IsUnpausing + { + get => _isUnpausing.Switch(); + set + { + ArgumentExceptionHelper.ThrowIfNull(value); + _isUnpausing.OnNext(value); + } + } + + /// + /// Gets or sets an observable which signals when the application is continuing from a temporarily paused state. + /// + /// + /// This member exists to preserve behavior patterns where a host differentiates resume-from-tombstone vs + /// resume-from-suspend; consumers may ignore it if not applicable. + /// + /// Thrown when the value is . + public IObservable IsContinuing + { + get => _isContinuing.Switch(); + set + { + ArgumentExceptionHelper.ThrowIfNull(value); + _isContinuing.OnNext(value); + } + } + + /// + /// Gets or sets the observable which signals when the application should persist its state to disk. + /// + /// + /// The produced should be disposed once the application finishes persisting its state. + /// + /// Thrown when the value is . + public IObservable ShouldPersistState + { + get => _shouldPersistState.Switch(); + set + { + ArgumentExceptionHelper.ThrowIfNull(value); + _shouldPersistState.OnNext(value); + } + } + + /// + /// Gets or sets the observable which signals that the saved application state should be deleted. + /// + /// + /// Triggered when the host detects an unrecoverable failure; use it to delete corrupt state and log crash telemetry. + /// + /// Thrown when the value is . + public IObservable ShouldInvalidateState + { + get => _shouldInvalidateState.Switch(); + set + { + ArgumentExceptionHelper.ThrowIfNull(value); + _shouldInvalidateState.OnNext(value); + } + } + + /// + /// Gets or sets a function that can be used to create a new application state instance. + /// + /// + /// This is the typed counterpart to and is typically used when the + /// application is launching fresh or recovering from an invalidated state. + /// + public Func? CreateNewAppStateTyped + { + get => _createNewAppStateTyped; + set => this.RaiseAndSetIfChanged(ref _createNewAppStateTyped, value); + } + + /// + /// Gets or sets the current application state. + /// + /// + /// This is the typed counterpart to . + /// + public TAppState? AppStateValue + { + get => _appState; + set + { + // Keep ReactiveObject semantics for existing consumers. + this.RaiseAndSetIfChanged(ref _appState, value); + + // Publish change notification for trimming/AOT-friendly observation. + _appStateValueChanged.OnNext(value); + } + } + + /// + /// Gets an observable that signals when is assigned. + /// + /// + /// This is a trimming/AOT-friendly change signal. It is independent of ReactiveUI's WhenAny APIs. + /// + public IObservable AppStateValueChanged => _appStateValueChanged; + + /// + /// Gets or sets a function that can be used to create a new application state instance. + /// + /// + /// This is the legacy object-based API. It projects to/from . + /// + /// + /// Thrown when the factory returns a value that is not assignable to . + /// + Func? ISuspensionHost.CreateNewAppState + { + get + { + var typedFactory = _createNewAppStateTyped; + Func? returnFunc = typedFactory is null ? null : () => typedFactory.Invoke()!; + + return returnFunc; + } + + set + { + if (value is null) + { + CreateNewAppStateTyped = null; + return; + } + + CreateNewAppStateTyped = () => + { + var created = value(); + return created is TAppState typed + ? typed + : throw new InvalidCastException($"Created app state is not assignable to {typeof(TAppState).FullName}."); + }; + } + } + + /// + /// Gets or sets the current application state. + /// + /// + /// This is the legacy object-based API. It projects to/from the typed property. + /// + /// + /// Thrown when the assigned value is not assignable to . + /// + object? ISuspensionHost.AppState + { + get => _appState; + set + { + if (value is null) + { + AppStateValue = default; + return; + } + + if (value is not TAppState typed) + { + throw new InvalidCastException($"AppState is not assignable to {typeof(TAppState).FullName}."); + } + + AppStateValue = typed; + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases managed resources used by the instance. + /// + /// + /// to release managed resources; to release unmanaged resources only. + /// + protected virtual void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + + _isLaunchingNew.Dispose(); + _isResuming.Dispose(); + _isUnpausing.Dispose(); + _isContinuing.Dispose(); + _shouldPersistState.Dispose(); + _shouldInvalidateState.Dispose(); + + _appStateValueChanged.Dispose(); + } +} diff --git a/src/ReactiveUI/VariadicTemplates.cs b/src/ReactiveUI/VariadicTemplates.cs index b77aac1d6a..3f6b1e1696 100644 --- a/src/ReactiveUI/VariadicTemplates.cs +++ b/src/ReactiveUI/VariadicTemplates.cs @@ -15,5954 +15,5273 @@ //------------------------------------------------------------------------------ using System; -using System.Diagnostics.CodeAnalysis; +using System.Reactive.Linq; using System.Linq; using System.Linq.Expressions; -using System.Reactive.Linq; - +using System.Diagnostics.CodeAnalysis; -namespace ReactiveUI; -/// Extension methods associated with the WhenAny/WhenAnyValue classes. -public static class WhenAnyMixin +namespace ReactiveUI { - /// - /// WhenAnyValue allows you to observe whenever the value of a - /// property on an object has changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The first property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1) - { - return sender!.WhenAny(property1, (IObservedChange c1) => c1.Value); - } + /// Extension methods associated with the WhenAny/WhenAnyValue classes. + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + public static class WhenAnyMixin + { + /// + /// WhenAnyValue allows you to observe whenever the value of a + /// property on an object has changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The first property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1) + { + return sender!.WhenAny(property1, (IObservedChange c1) => c1.Value); + } - /// - /// AOT-friendly overload that avoids expression trees by using a property name. - /// - /// The object where the property chain starts. - /// The property name to observe. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string propertyName) - { - return sender!.ObservableForProperty(propertyName, beforeChange: false, skipInitial: false, isDistinct: true) - .Select(x => x.Value); - } + /// + /// AOT-friendly overload that avoids expression trees by using a property name. + /// + /// The object where the property chain starts. + /// The property name to observe. + public static IObservable WhenAnyValue( + this TSender? sender, + string propertyName) + { + return sender!.ObservableForProperty(propertyName, beforeChange: false, skipInitial: false, isDistinct: true) + .Select(x => x.Value); + } - /// - /// WhenAnyValue allows you to observe whenever the value of a - /// property on an object has changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The first property chain to reference. This will be a expression pointing to a end property or field. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - bool isDistinct) - { - return sender!.WhenAny(property1, (IObservedChange c1) => c1.Value, isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of a + /// property on an object has changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The first property chain to reference. This will be a expression pointing to a end property or field. + /// if set to true [is distinct]. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + bool isDistinct) + { + return sender!.WhenAny(property1, (IObservedChange c1) => c1.Value, isDistinct); + } - /// - /// AOT-friendly overload that avoids expression trees by using a property name and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string propertyName, - bool isDistinct) - { - return sender!.ObservableForProperty(propertyName, beforeChange: false, skipInitial: false, isDistinct: isDistinct) - .Select(x => x.Value); - } + /// + /// AOT-friendly overload that avoids expression trees by using a property name and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string propertyName, + bool isDistinct) + { + return sender!.ObservableForProperty(propertyName, beforeChange: false, skipInitial: false, isDistinct: isDistinct) + .Select(x => x.Value); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Func selector) - { - return sender!.WhenAny(property1, - (c1) => - selector(c1.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Func selector) + { + return sender!.WhenAny(property1, + (c1) => + selector(c1.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return o1.Select(selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return o1.Select(selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, - (c1) => - selector(c1.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, + (c1) => + selector(c1.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return o1.Select(selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return o1.Select(selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Func, TRet> selector) - { - return sender!.ObservableForProperty(property1, false, false).Select(selector); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Func, TRet> selector) + { + return sender!.ObservableForProperty(property1, false, false).Select(selector); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - Func, TRet> selector) - { - return sender!.ObservableForProperty(property1Name, false, false) - .Select(c1 => selector(c1)); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + Func, TRet> selector) + { + return sender!.ObservableForProperty(property1Name, false, false) + .Select(c1 => selector(c1)); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Func, TRet> selector, - bool isDistinct) - { - return sender!.ObservableForProperty(property1, false, false, isDistinct).Select(selector); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Func, TRet> selector, + bool isDistinct) + { + return sender!.ObservableForProperty(property1, false, false, isDistinct).Select(selector); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - Func, TRet> selector, - bool isDistinct) - { - return sender!.ObservableForProperty(property1Name, false, false, isDistinct) - .Select(c1 => selector(c1)); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + Func, TRet> selector, + bool isDistinct) + { + return sender!.ObservableForProperty(property1Name, false, false, isDistinct) + .Select(c1 => selector(c1)); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Func, TRet> selector) - { - return ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false).Select(selector); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Func, TRet> selector) + { + return ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false).Select(selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Func, TRet> selector, - bool isDistinct) - { - return ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct).Select(selector); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Func, TRet> selector, + bool isDistinct) + { + return ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct).Select(selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2 - ) - { - return sender!.WhenAny(property1, property2, - (c1, c2) => - (c1.Value, c2.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2 + ) + { + return sender!.WhenAny(property1, property2, + (c1, c2) => + (c1.Value, c2.Value)); + } - /// - /// AOT-friendly tuple overloads using property names instead of expressions. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name ) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value) - , (v1,v2) => - (v1,v2) - ); - } + /// + /// AOT-friendly tuple overloads using property names instead of expressions. + /// + public static IObservable<(T1,T2)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name ) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value) + , (v1,v2) => + (v1,v2) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2 - , - bool isDistinct) - { - return sender!.WhenAny(property1, property2, - (c1, c2) => - (c1.Value, c2.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2 + , + bool isDistinct) + { + return sender!.WhenAny(property1, property2, + (c1, c2) => + (c1.Value, c2.Value), + isDistinct); + } - /// - /// AOT-friendly tuple overloads using property names with distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name , - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value) - , (v1,v2) => - (v1,v2) - ); - } + /// + /// AOT-friendly tuple overloads using property names with distinct option. + /// + public static IObservable<(T1,T2)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name , + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value) + , (v1,v2) => + (v1,v2) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Func selector) - { - return sender!.WhenAny(property1, property2, - (c1, c2) => - selector(c1.Value, c2.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Func selector) + { + return sender!.WhenAny(property1, property2, + (c1, c2) => + selector(c1.Value, c2.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, - (c1, c2) => - selector(c1.Value, c2.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, + (c1, c2) => + selector(c1.Value, c2.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Func, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Func, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - Func, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + Func, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Func, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Func, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - Func, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + Func, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Func, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Func, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Func, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Func, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3 - ) - { - return sender!.WhenAny(property1, property2, property3, - (c1, c2, c3) => - (c1.Value, c2.Value, c3.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3 + ) + { + return sender!.WhenAny(property1, property2, property3, + (c1, c2, c3) => + (c1.Value, c2.Value, c3.Value)); + } - /// - /// AOT-friendly tuple overloads using property names instead of expressions. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name ) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value) - , (v1,v2,v3) => - (v1,v2,v3) - ); - } + /// + /// AOT-friendly tuple overloads using property names instead of expressions. + /// + public static IObservable<(T1,T2,T3)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name ) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value) + , (v1,v2,v3) => + (v1,v2,v3) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3 - , - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, - (c1, c2, c3) => - (c1.Value, c2.Value, c3.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3 + , + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, + (c1, c2, c3) => + (c1.Value, c2.Value, c3.Value), + isDistinct); + } - /// - /// AOT-friendly tuple overloads using property names with distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name , - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value) - , (v1,v2,v3) => - (v1,v2,v3) - ); - } + /// + /// AOT-friendly tuple overloads using property names with distinct option. + /// + public static IObservable<(T1,T2,T3)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name , + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value) + , (v1,v2,v3) => + (v1,v2,v3) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, - (c1, c2, c3) => - selector(c1.Value, c2.Value, c3.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, + (c1, c2, c3) => + selector(c1.Value, c2.Value, c3.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, - (c1, c2, c3) => - selector(c1.Value, c2.Value, c3.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, + (c1, c2, c3) => + selector(c1.Value, c2.Value, c3.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Func, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Func, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - Func, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + Func, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Func, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Func, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - Func, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + Func, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Func, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Func, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Func, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Func, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4 - ) - { - return sender!.WhenAny(property1, property2, property3, property4, - (c1, c2, c3, c4) => - (c1.Value, c2.Value, c3.Value, c4.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3,T4)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4 + ) + { + return sender!.WhenAny(property1, property2, property3, property4, + (c1, c2, c3, c4) => + (c1.Value, c2.Value, c3.Value, c4.Value)); + } - /// - /// AOT-friendly tuple overloads using property names instead of expressions. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name, string property4Name ) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value), - o4.Select(x => x.Value) - , (v1,v2,v3,v4) => - (v1,v2,v3,v4) - ); - } + /// + /// AOT-friendly tuple overloads using property names instead of expressions. + /// + public static IObservable<(T1,T2,T3,T4)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name, string property4Name ) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value), + o4.Select(x => x.Value) + , (v1,v2,v3,v4) => + (v1,v2,v3,v4) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4 - , - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, - (c1, c2, c3, c4) => - (c1.Value, c2.Value, c3.Value, c4.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3,T4)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4 + , + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, + (c1, c2, c3, c4) => + (c1.Value, c2.Value, c3.Value, c4.Value), + isDistinct); + } - /// - /// AOT-friendly tuple overloads using property names with distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name, string property4Name , - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value), - o4.Select(x => x.Value) - , (v1,v2,v3,v4) => - (v1,v2,v3,v4) - ); - } + /// + /// AOT-friendly tuple overloads using property names with distinct option. + /// + public static IObservable<(T1,T2,T3,T4)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name, string property4Name , + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value), + o4.Select(x => x.Value) + , (v1,v2,v3,v4) => + (v1,v2,v3,v4) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, property4, - (c1, c2, c3, c4) => - selector(c1.Value, c2.Value, c3.Value, c4.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, property4, + (c1, c2, c3, c4) => + selector(c1.Value, c2.Value, c3.Value, c4.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, - (c1, c2, c3, c4) => - selector(c1.Value, c2.Value, c3.Value, c4.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, + (c1, c2, c3, c4) => + selector(c1.Value, c2.Value, c3.Value, c4.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - sender!.ObservableForProperty(property4, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + sender!.ObservableForProperty(property4, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - sender!.ObservableForProperty(property4Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + sender!.ObservableForProperty(property4Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - sender!.ObservableForProperty(property4, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + sender!.ObservableForProperty(property4, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - sender!.ObservableForProperty(property4Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + sender!.ObservableForProperty(property4Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Func, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5 - ) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, - (c1, c2, c3, c4, c5) => - (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3,T4,T5)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5 + ) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, + (c1, c2, c3, c4, c5) => + (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value)); + } - /// - /// AOT-friendly tuple overloads using property names instead of expressions. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name, string property4Name, string property5Name ) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value), - o4.Select(x => x.Value), - o5.Select(x => x.Value) - , (v1,v2,v3,v4,v5) => - (v1,v2,v3,v4,v5) - ); - } + /// + /// AOT-friendly tuple overloads using property names instead of expressions. + /// + public static IObservable<(T1,T2,T3,T4,T5)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name, string property4Name, string property5Name ) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value), + o4.Select(x => x.Value), + o5.Select(x => x.Value) + , (v1,v2,v3,v4,v5) => + (v1,v2,v3,v4,v5) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5 - , - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, - (c1, c2, c3, c4, c5) => - (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3,T4,T5)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5 + , + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, + (c1, c2, c3, c4, c5) => + (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value), + isDistinct); + } - /// - /// AOT-friendly tuple overloads using property names with distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name, string property4Name, string property5Name , - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value), - o4.Select(x => x.Value), - o5.Select(x => x.Value) - , (v1,v2,v3,v4,v5) => - (v1,v2,v3,v4,v5) - ); - } + /// + /// AOT-friendly tuple overloads using property names with distinct option. + /// + public static IObservable<(T1,T2,T3,T4,T5)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name, string property4Name, string property5Name , + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value), + o4.Select(x => x.Value), + o5.Select(x => x.Value) + , (v1,v2,v3,v4,v5) => + (v1,v2,v3,v4,v5) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, - (c1, c2, c3, c4, c5) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, + (c1, c2, c3, c4, c5) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, - (c1, c2, c3, c4, c5) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, + (c1, c2, c3, c4, c5) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - sender!.ObservableForProperty(property4, false, false), - sender!.ObservableForProperty(property5, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + sender!.ObservableForProperty(property4, false, false), + sender!.ObservableForProperty(property5, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - sender!.ObservableForProperty(property4Name, false, false), - sender!.ObservableForProperty(property5Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + sender!.ObservableForProperty(property4Name, false, false), + sender!.ObservableForProperty(property5Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - sender!.ObservableForProperty(property4, false, false, isDistinct), - sender!.ObservableForProperty(property5, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + sender!.ObservableForProperty(property4, false, false, isDistinct), + sender!.ObservableForProperty(property5, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - sender!.ObservableForProperty(property4Name, false, false, isDistinct), - sender!.ObservableForProperty(property5Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + sender!.ObservableForProperty(property4Name, false, false, isDistinct), + sender!.ObservableForProperty(property5Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5,T6)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6 - ) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, - (c1, c2, c3, c4, c5, c6) => - (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3,T4,T5,T6)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6 + ) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, + (c1, c2, c3, c4, c5, c6) => + (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value)); + } - /// - /// AOT-friendly tuple overloads using property names instead of expressions. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5,T6)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name ) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value), - o4.Select(x => x.Value), - o5.Select(x => x.Value), - o6.Select(x => x.Value) - , (v1,v2,v3,v4,v5,v6) => - (v1,v2,v3,v4,v5,v6) - ); - } + /// + /// AOT-friendly tuple overloads using property names instead of expressions. + /// + public static IObservable<(T1,T2,T3,T4,T5,T6)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name ) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value), + o4.Select(x => x.Value), + o5.Select(x => x.Value), + o6.Select(x => x.Value) + , (v1,v2,v3,v4,v5,v6) => + (v1,v2,v3,v4,v5,v6) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5,T6)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6 - , - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, - (c1, c2, c3, c4, c5, c6) => - (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3,T4,T5,T6)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6 + , + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, + (c1, c2, c3, c4, c5, c6) => + (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value), + isDistinct); + } - /// - /// AOT-friendly tuple overloads using property names with distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5,T6)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name , - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value), - o4.Select(x => x.Value), - o5.Select(x => x.Value), - o6.Select(x => x.Value) - , (v1,v2,v3,v4,v5,v6) => - (v1,v2,v3,v4,v5,v6) - ); - } + /// + /// AOT-friendly tuple overloads using property names with distinct option. + /// + public static IObservable<(T1,T2,T3,T4,T5,T6)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name , + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value), + o4.Select(x => x.Value), + o5.Select(x => x.Value), + o6.Select(x => x.Value) + , (v1,v2,v3,v4,v5,v6) => + (v1,v2,v3,v4,v5,v6) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, - (c1, c2, c3, c4, c5, c6) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, + (c1, c2, c3, c4, c5, c6) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, - (c1, c2, c3, c4, c5, c6) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, + (c1, c2, c3, c4, c5, c6) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - sender!.ObservableForProperty(property4, false, false), - sender!.ObservableForProperty(property5, false, false), - sender!.ObservableForProperty(property6, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + sender!.ObservableForProperty(property4, false, false), + sender!.ObservableForProperty(property5, false, false), + sender!.ObservableForProperty(property6, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - sender!.ObservableForProperty(property4Name, false, false), - sender!.ObservableForProperty(property5Name, false, false), - sender!.ObservableForProperty(property6Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + sender!.ObservableForProperty(property4Name, false, false), + sender!.ObservableForProperty(property5Name, false, false), + sender!.ObservableForProperty(property6Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - sender!.ObservableForProperty(property4, false, false, isDistinct), - sender!.ObservableForProperty(property5, false, false, isDistinct), - sender!.ObservableForProperty(property6, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + sender!.ObservableForProperty(property4, false, false, isDistinct), + sender!.ObservableForProperty(property5, false, false, isDistinct), + sender!.ObservableForProperty(property6, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - sender!.ObservableForProperty(property4Name, false, false, isDistinct), - sender!.ObservableForProperty(property5Name, false, false, isDistinct), - sender!.ObservableForProperty(property6Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + sender!.ObservableForProperty(property4Name, false, false, isDistinct), + sender!.ObservableForProperty(property5Name, false, false, isDistinct), + sender!.ObservableForProperty(property6Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5,T6,T7)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7 - ) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, - (c1, c2, c3, c4, c5, c6, c7) => - (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3,T4,T5,T6,T7)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7 + ) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, + (c1, c2, c3, c4, c5, c6, c7) => + (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value)); + } - /// - /// AOT-friendly tuple overloads using property names instead of expressions. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5,T6,T7)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name ) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value), - o4.Select(x => x.Value), - o5.Select(x => x.Value), - o6.Select(x => x.Value), - o7.Select(x => x.Value) - , (v1,v2,v3,v4,v5,v6,v7) => - (v1,v2,v3,v4,v5,v6,v7) - ); - } + /// + /// AOT-friendly tuple overloads using property names instead of expressions. + /// + public static IObservable<(T1,T2,T3,T4,T5,T6,T7)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name ) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value), + o4.Select(x => x.Value), + o5.Select(x => x.Value), + o6.Select(x => x.Value), + o7.Select(x => x.Value) + , (v1,v2,v3,v4,v5,v6,v7) => + (v1,v2,v3,v4,v5,v6,v7) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5,T6,T7)> WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7 - , - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, - (c1, c2, c3, c4, c5, c6, c7) => - (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + public static IObservable<(T1,T2,T3,T4,T5,T6,T7)> WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7 + , + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, + (c1, c2, c3, c4, c5, c6, c7) => + (c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value), + isDistinct); + } - /// - /// AOT-friendly tuple overloads using property names with distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable<(T1,T2,T3,T4,T5,T6,T7)> WhenAnyValue( - this TSender? sender, - string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name , - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); - return Observable.CombineLatest( - o1.Select(x => x.Value), - o2.Select(x => x.Value), - o3.Select(x => x.Value), - o4.Select(x => x.Value), - o5.Select(x => x.Value), - o6.Select(x => x.Value), - o7.Select(x => x.Value) - , (v1,v2,v3,v4,v5,v6,v7) => - (v1,v2,v3,v4,v5,v6,v7) - ); - } + /// + /// AOT-friendly tuple overloads using property names with distinct option. + /// + public static IObservable<(T1,T2,T3,T4,T5,T6,T7)> WhenAnyValue( + this TSender? sender, + string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name , + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct); + return Observable.CombineLatest( + o1.Select(x => x.Value), + o2.Select(x => x.Value), + o3.Select(x => x.Value), + o4.Select(x => x.Value), + o5.Select(x => x.Value), + o6.Select(x => x.Value), + o7.Select(x => x.Value) + , (v1,v2,v3,v4,v5,v6,v7) => + (v1,v2,v3,v4,v5,v6,v7) + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, - (c1, c2, c3, c4, c5, c6, c7) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, + (c1, c2, c3, c4, c5, c6, c7) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, - (c1, c2, c3, c4, c5, c6, c7) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, + (c1, c2, c3, c4, c5, c6, c7) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - sender!.ObservableForProperty(property4, false, false), - sender!.ObservableForProperty(property5, false, false), - sender!.ObservableForProperty(property6, false, false), - sender!.ObservableForProperty(property7, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + sender!.ObservableForProperty(property4, false, false), + sender!.ObservableForProperty(property5, false, false), + sender!.ObservableForProperty(property6, false, false), + sender!.ObservableForProperty(property7, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - sender!.ObservableForProperty(property4Name, false, false), - sender!.ObservableForProperty(property5Name, false, false), - sender!.ObservableForProperty(property6Name, false, false), - sender!.ObservableForProperty(property7Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + sender!.ObservableForProperty(property4Name, false, false), + sender!.ObservableForProperty(property5Name, false, false), + sender!.ObservableForProperty(property6Name, false, false), + sender!.ObservableForProperty(property7Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - sender!.ObservableForProperty(property4, false, false, isDistinct), - sender!.ObservableForProperty(property5, false, false, isDistinct), - sender!.ObservableForProperty(property6, false, false, isDistinct), - sender!.ObservableForProperty(property7, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + sender!.ObservableForProperty(property4, false, false, isDistinct), + sender!.ObservableForProperty(property5, false, false, isDistinct), + sender!.ObservableForProperty(property6, false, false, isDistinct), + sender!.ObservableForProperty(property7, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - sender!.ObservableForProperty(property4Name, false, false, isDistinct), - sender!.ObservableForProperty(property5Name, false, false, isDistinct), - sender!.ObservableForProperty(property6Name, false, false, isDistinct), - sender!.ObservableForProperty(property7Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + sender!.ObservableForProperty(property4Name, false, false, isDistinct), + sender!.ObservableForProperty(property5Name, false, false, isDistinct), + sender!.ObservableForProperty(property6Name, false, false, isDistinct), + sender!.ObservableForProperty(property7Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, - (c1, c2, c3, c4, c5, c6, c7, c8) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, + (c1, c2, c3, c4, c5, c6, c7, c8) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, - (c1, c2, c3, c4, c5, c6, c7, c8) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, + (c1, c2, c3, c4, c5, c6, c7, c8) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - sender!.ObservableForProperty(property4, false, false), - sender!.ObservableForProperty(property5, false, false), - sender!.ObservableForProperty(property6, false, false), - sender!.ObservableForProperty(property7, false, false), - sender!.ObservableForProperty(property8, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + sender!.ObservableForProperty(property4, false, false), + sender!.ObservableForProperty(property5, false, false), + sender!.ObservableForProperty(property6, false, false), + sender!.ObservableForProperty(property7, false, false), + sender!.ObservableForProperty(property8, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - sender!.ObservableForProperty(property4Name, false, false), - sender!.ObservableForProperty(property5Name, false, false), - sender!.ObservableForProperty(property6Name, false, false), - sender!.ObservableForProperty(property7Name, false, false), - sender!.ObservableForProperty(property8Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + sender!.ObservableForProperty(property4Name, false, false), + sender!.ObservableForProperty(property5Name, false, false), + sender!.ObservableForProperty(property6Name, false, false), + sender!.ObservableForProperty(property7Name, false, false), + sender!.ObservableForProperty(property8Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - sender!.ObservableForProperty(property4, false, false, isDistinct), - sender!.ObservableForProperty(property5, false, false, isDistinct), - sender!.ObservableForProperty(property6, false, false, isDistinct), - sender!.ObservableForProperty(property7, false, false, isDistinct), - sender!.ObservableForProperty(property8, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + sender!.ObservableForProperty(property4, false, false, isDistinct), + sender!.ObservableForProperty(property5, false, false, isDistinct), + sender!.ObservableForProperty(property6, false, false, isDistinct), + sender!.ObservableForProperty(property7, false, false, isDistinct), + sender!.ObservableForProperty(property8, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - sender!.ObservableForProperty(property4Name, false, false, isDistinct), - sender!.ObservableForProperty(property5Name, false, false, isDistinct), - sender!.ObservableForProperty(property6Name, false, false, isDistinct), - sender!.ObservableForProperty(property7Name, false, false, isDistinct), - sender!.ObservableForProperty(property8Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + sender!.ObservableForProperty(property4Name, false, false, isDistinct), + sender!.ObservableForProperty(property5Name, false, false, isDistinct), + sender!.ObservableForProperty(property6Name, false, false, isDistinct), + sender!.ObservableForProperty(property7Name, false, false, isDistinct), + sender!.ObservableForProperty(property8Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, - (c1, c2, c3, c4, c5, c6, c7, c8, c9) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, + (c1, c2, c3, c4, c5, c6, c7, c8, c9) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8, - o9 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o9 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, - (c1, c2, c3, c4, c5, c6, c7, c8, c9) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, + (c1, c2, c3, c4, c5, c6, c7, c8, c9) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8, - o9 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o9 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - sender!.ObservableForProperty(property4, false, false), - sender!.ObservableForProperty(property5, false, false), - sender!.ObservableForProperty(property6, false, false), - sender!.ObservableForProperty(property7, false, false), - sender!.ObservableForProperty(property8, false, false), - sender!.ObservableForProperty(property9, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + sender!.ObservableForProperty(property4, false, false), + sender!.ObservableForProperty(property5, false, false), + sender!.ObservableForProperty(property6, false, false), + sender!.ObservableForProperty(property7, false, false), + sender!.ObservableForProperty(property8, false, false), + sender!.ObservableForProperty(property9, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - sender!.ObservableForProperty(property4Name, false, false), - sender!.ObservableForProperty(property5Name, false, false), - sender!.ObservableForProperty(property6Name, false, false), - sender!.ObservableForProperty(property7Name, false, false), - sender!.ObservableForProperty(property8Name, false, false), - sender!.ObservableForProperty(property9Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + sender!.ObservableForProperty(property4Name, false, false), + sender!.ObservableForProperty(property5Name, false, false), + sender!.ObservableForProperty(property6Name, false, false), + sender!.ObservableForProperty(property7Name, false, false), + sender!.ObservableForProperty(property8Name, false, false), + sender!.ObservableForProperty(property9Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - sender!.ObservableForProperty(property4, false, false, isDistinct), - sender!.ObservableForProperty(property5, false, false, isDistinct), - sender!.ObservableForProperty(property6, false, false, isDistinct), - sender!.ObservableForProperty(property7, false, false, isDistinct), - sender!.ObservableForProperty(property8, false, false, isDistinct), - sender!.ObservableForProperty(property9, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + sender!.ObservableForProperty(property4, false, false, isDistinct), + sender!.ObservableForProperty(property5, false, false, isDistinct), + sender!.ObservableForProperty(property6, false, false, isDistinct), + sender!.ObservableForProperty(property7, false, false, isDistinct), + sender!.ObservableForProperty(property8, false, false, isDistinct), + sender!.ObservableForProperty(property9, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - sender!.ObservableForProperty(property4Name, false, false, isDistinct), - sender!.ObservableForProperty(property5Name, false, false, isDistinct), - sender!.ObservableForProperty(property6Name, false, false, isDistinct), - sender!.ObservableForProperty(property7Name, false, false, isDistinct), - sender!.ObservableForProperty(property8Name, false, false, isDistinct), - sender!.ObservableForProperty(property9Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + sender!.ObservableForProperty(property4Name, false, false, isDistinct), + sender!.ObservableForProperty(property5Name, false, false, isDistinct), + sender!.ObservableForProperty(property6Name, false, false, isDistinct), + sender!.ObservableForProperty(property7Name, false, false, isDistinct), + sender!.ObservableForProperty(property8Name, false, false, isDistinct), + sender!.ObservableForProperty(property9Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Expression? property9, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property9, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Expression? property9, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property9, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Expression? property9, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property9, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Expression? property9, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property9, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8, - o9, - o10 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o9, + o10 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8, - o9, - o10 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o9, + o10 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - sender!.ObservableForProperty(property4, false, false), - sender!.ObservableForProperty(property5, false, false), - sender!.ObservableForProperty(property6, false, false), - sender!.ObservableForProperty(property7, false, false), - sender!.ObservableForProperty(property8, false, false), - sender!.ObservableForProperty(property9, false, false), - sender!.ObservableForProperty(property10, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + sender!.ObservableForProperty(property4, false, false), + sender!.ObservableForProperty(property5, false, false), + sender!.ObservableForProperty(property6, false, false), + sender!.ObservableForProperty(property7, false, false), + sender!.ObservableForProperty(property8, false, false), + sender!.ObservableForProperty(property9, false, false), + sender!.ObservableForProperty(property10, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - sender!.ObservableForProperty(property4Name, false, false), - sender!.ObservableForProperty(property5Name, false, false), - sender!.ObservableForProperty(property6Name, false, false), - sender!.ObservableForProperty(property7Name, false, false), - sender!.ObservableForProperty(property8Name, false, false), - sender!.ObservableForProperty(property9Name, false, false), - sender!.ObservableForProperty(property10Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + sender!.ObservableForProperty(property4Name, false, false), + sender!.ObservableForProperty(property5Name, false, false), + sender!.ObservableForProperty(property6Name, false, false), + sender!.ObservableForProperty(property7Name, false, false), + sender!.ObservableForProperty(property8Name, false, false), + sender!.ObservableForProperty(property9Name, false, false), + sender!.ObservableForProperty(property10Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - sender!.ObservableForProperty(property4, false, false, isDistinct), - sender!.ObservableForProperty(property5, false, false, isDistinct), - sender!.ObservableForProperty(property6, false, false, isDistinct), - sender!.ObservableForProperty(property7, false, false, isDistinct), - sender!.ObservableForProperty(property8, false, false, isDistinct), - sender!.ObservableForProperty(property9, false, false, isDistinct), - sender!.ObservableForProperty(property10, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + sender!.ObservableForProperty(property4, false, false, isDistinct), + sender!.ObservableForProperty(property5, false, false, isDistinct), + sender!.ObservableForProperty(property6, false, false, isDistinct), + sender!.ObservableForProperty(property7, false, false, isDistinct), + sender!.ObservableForProperty(property8, false, false, isDistinct), + sender!.ObservableForProperty(property9, false, false, isDistinct), + sender!.ObservableForProperty(property10, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - sender!.ObservableForProperty(property4Name, false, false, isDistinct), - sender!.ObservableForProperty(property5Name, false, false, isDistinct), - sender!.ObservableForProperty(property6Name, false, false, isDistinct), - sender!.ObservableForProperty(property7Name, false, false, isDistinct), - sender!.ObservableForProperty(property8Name, false, false, isDistinct), - sender!.ObservableForProperty(property9Name, false, false, isDistinct), - sender!.ObservableForProperty(property10Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + sender!.ObservableForProperty(property4Name, false, false, isDistinct), + sender!.ObservableForProperty(property5Name, false, false, isDistinct), + sender!.ObservableForProperty(property6Name, false, false, isDistinct), + sender!.ObservableForProperty(property7Name, false, false, isDistinct), + sender!.ObservableForProperty(property8Name, false, false, isDistinct), + sender!.ObservableForProperty(property9Name, false, false, isDistinct), + sender!.ObservableForProperty(property10Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Expression? property9, - Expression? property10, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property9, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property10, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Expression? property9, + Expression? property10, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property9, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property10, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Expression? property9, - Expression? property10, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property9, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property10, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Expression? property9, + Expression? property10, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property9, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property10, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Expression> property11, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, property11, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value, c11.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Expression> property11, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, property11, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value, c11.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - string property11Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o11 = sender!.ObservableForProperty(property11Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8, - o9, - o10, - o11 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + string property11Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o11 = sender!.ObservableForProperty(property11Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o9, + o10, + o11 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Expression> property11, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, property11, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value, c11.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Expression> property11, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, property11, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value, c11.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - string property11Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o11 = sender!.ObservableForProperty(property11Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8, - o9, - o10, - o11 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + string property11Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o11 = sender!.ObservableForProperty(property11Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o9, + o10, + o11 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Expression> property11, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - sender!.ObservableForProperty(property4, false, false), - sender!.ObservableForProperty(property5, false, false), - sender!.ObservableForProperty(property6, false, false), - sender!.ObservableForProperty(property7, false, false), - sender!.ObservableForProperty(property8, false, false), - sender!.ObservableForProperty(property9, false, false), - sender!.ObservableForProperty(property10, false, false), - sender!.ObservableForProperty(property11, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Expression> property11, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + sender!.ObservableForProperty(property4, false, false), + sender!.ObservableForProperty(property5, false, false), + sender!.ObservableForProperty(property6, false, false), + sender!.ObservableForProperty(property7, false, false), + sender!.ObservableForProperty(property8, false, false), + sender!.ObservableForProperty(property9, false, false), + sender!.ObservableForProperty(property10, false, false), + sender!.ObservableForProperty(property11, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - string property11Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - sender!.ObservableForProperty(property4Name, false, false), - sender!.ObservableForProperty(property5Name, false, false), - sender!.ObservableForProperty(property6Name, false, false), - sender!.ObservableForProperty(property7Name, false, false), - sender!.ObservableForProperty(property8Name, false, false), - sender!.ObservableForProperty(property9Name, false, false), - sender!.ObservableForProperty(property10Name, false, false), - sender!.ObservableForProperty(property11Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + string property11Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + sender!.ObservableForProperty(property4Name, false, false), + sender!.ObservableForProperty(property5Name, false, false), + sender!.ObservableForProperty(property6Name, false, false), + sender!.ObservableForProperty(property7Name, false, false), + sender!.ObservableForProperty(property8Name, false, false), + sender!.ObservableForProperty(property9Name, false, false), + sender!.ObservableForProperty(property10Name, false, false), + sender!.ObservableForProperty(property11Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Expression> property11, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - sender!.ObservableForProperty(property4, false, false, isDistinct), - sender!.ObservableForProperty(property5, false, false, isDistinct), - sender!.ObservableForProperty(property6, false, false, isDistinct), - sender!.ObservableForProperty(property7, false, false, isDistinct), - sender!.ObservableForProperty(property8, false, false, isDistinct), - sender!.ObservableForProperty(property9, false, false, isDistinct), - sender!.ObservableForProperty(property10, false, false, isDistinct), - sender!.ObservableForProperty(property11, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Expression> property11, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + sender!.ObservableForProperty(property4, false, false, isDistinct), + sender!.ObservableForProperty(property5, false, false, isDistinct), + sender!.ObservableForProperty(property6, false, false, isDistinct), + sender!.ObservableForProperty(property7, false, false, isDistinct), + sender!.ObservableForProperty(property8, false, false, isDistinct), + sender!.ObservableForProperty(property9, false, false, isDistinct), + sender!.ObservableForProperty(property10, false, false, isDistinct), + sender!.ObservableForProperty(property11, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - string property11Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - sender!.ObservableForProperty(property4Name, false, false, isDistinct), - sender!.ObservableForProperty(property5Name, false, false, isDistinct), - sender!.ObservableForProperty(property6Name, false, false, isDistinct), - sender!.ObservableForProperty(property7Name, false, false, isDistinct), - sender!.ObservableForProperty(property8Name, false, false, isDistinct), - sender!.ObservableForProperty(property9Name, false, false, isDistinct), - sender!.ObservableForProperty(property10Name, false, false, isDistinct), - sender!.ObservableForProperty(property11Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + string property11Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + sender!.ObservableForProperty(property4Name, false, false, isDistinct), + sender!.ObservableForProperty(property5Name, false, false, isDistinct), + sender!.ObservableForProperty(property6Name, false, false, isDistinct), + sender!.ObservableForProperty(property7Name, false, false, isDistinct), + sender!.ObservableForProperty(property8Name, false, false, isDistinct), + sender!.ObservableForProperty(property9Name, false, false, isDistinct), + sender!.ObservableForProperty(property10Name, false, false, isDistinct), + sender!.ObservableForProperty(property11Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Expression? property9, - Expression? property10, - Expression? property11, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property9, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property10, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property11, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Expression? property9, + Expression? property10, + Expression? property11, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property9, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property10, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property11, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Expression? property9, - Expression? property10, - Expression? property11, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property9, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property10, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property11, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Expression? property9, + Expression? property10, + Expression? property11, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property9, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property10, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property11, false, false, isDistinct), + selector + ); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The 12 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Expression> property11, - Expression> property12, - Func selector) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, property11, property12, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value, c11.Value, c12.Value)); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The 12 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Expression> property11, + Expression> property12, + Func selector) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, property11, property12, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value, c11.Value, c12.Value)); + } - /// - /// AOT-friendly selector overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - string property11Name, - string property12Name, - Func selector) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o11 = sender!.ObservableForProperty(property11Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - var o12 = sender!.ObservableForProperty(property12Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8, - o9, - o10, - o11, - o12 - , selector); - } + /// + /// AOT-friendly selector overload using property names. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + string property11Name, + string property12Name, + Func selector) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o11 = sender!.ObservableForProperty(property11Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + var o12 = sender!.ObservableForProperty(property12Name, beforeChange: false, skipInitial: false, isDistinct: true).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o9, + o10, + o11, + o12 + , selector); + } - /// - /// WhenAnyValue allows you to observe whenever the value of one or more - /// properties on an object have changed, providing an initial value when - /// the Observable is set up, unlike ObservableForProperty(). Use this - /// method in constructors to set up bindings between properties that also - /// need an initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The 12 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Expression> property11, - Expression> property12, - Func selector, - bool isDistinct) - { - return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, property11, property12, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => - selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value, c11.Value, c12.Value), - isDistinct); - } + /// + /// WhenAnyValue allows you to observe whenever the value of one or more + /// properties on an object have changed, providing an initial value when + /// the Observable is set up, unlike ObservableForProperty(). Use this + /// method in constructors to set up bindings between properties that also + /// need an initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The 12 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyValue( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Expression> property11, + Expression> property12, + Func selector, + bool isDistinct) + { + return sender!.WhenAny(property1, property2, property3, property4, property5, property6, property7, property8, property9, property10, property11, property12, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => + selector(c1.Value, c2.Value, c3.Value, c4.Value, c5.Value, c6.Value, c7.Value, c8.Value, c9.Value, c10.Value, c11.Value, c12.Value), + isDistinct); + } - /// - /// AOT-friendly selector overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyValue( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - string property11Name, - string property12Name, - Func selector, - bool isDistinct) - { - var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o11 = sender!.ObservableForProperty(property11Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - var o12 = sender!.ObservableForProperty(property12Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); - return Observable.CombineLatest( - o1, - o2, - o3, - o4, - o5, - o6, - o7, - o8, - o9, - o10, - o11, - o12 - , selector); - } + /// + /// AOT-friendly selector overload using property names and distinct option. + /// + public static IObservable WhenAnyValue( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + string property11Name, + string property12Name, + Func selector, + bool isDistinct) + { + var o1 = sender!.ObservableForProperty(property1Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o2 = sender!.ObservableForProperty(property2Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o3 = sender!.ObservableForProperty(property3Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o4 = sender!.ObservableForProperty(property4Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o5 = sender!.ObservableForProperty(property5Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o6 = sender!.ObservableForProperty(property6Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o7 = sender!.ObservableForProperty(property7Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o8 = sender!.ObservableForProperty(property8Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o9 = sender!.ObservableForProperty(property9Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o10 = sender!.ObservableForProperty(property10Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o11 = sender!.ObservableForProperty(property11Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + var o12 = sender!.ObservableForProperty(property12Name, beforeChange: false, skipInitial: false, isDistinct: isDistinct).Select(x => x.Value); + return Observable.CombineLatest( + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o9, + o10, + o11, + o12 + , selector); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The 12 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Expression> property11, - Expression> property12, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false), - sender!.ObservableForProperty(property2, false, false), - sender!.ObservableForProperty(property3, false, false), - sender!.ObservableForProperty(property4, false, false), - sender!.ObservableForProperty(property5, false, false), - sender!.ObservableForProperty(property6, false, false), - sender!.ObservableForProperty(property7, false, false), - sender!.ObservableForProperty(property8, false, false), - sender!.ObservableForProperty(property9, false, false), - sender!.ObservableForProperty(property10, false, false), - sender!.ObservableForProperty(property11, false, false), - sender!.ObservableForProperty(property12, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The 12 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Expression> property11, + Expression> property12, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false), + sender!.ObservableForProperty(property2, false, false), + sender!.ObservableForProperty(property3, false, false), + sender!.ObservableForProperty(property4, false, false), + sender!.ObservableForProperty(property5, false, false), + sender!.ObservableForProperty(property6, false, false), + sender!.ObservableForProperty(property7, false, false), + sender!.ObservableForProperty(property8, false, false), + sender!.ObservableForProperty(property9, false, false), + sender!.ObservableForProperty(property10, false, false), + sender!.ObservableForProperty(property11, false, false), + sender!.ObservableForProperty(property12, false, false), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - string property11Name, - string property12Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false), - sender!.ObservableForProperty(property2Name, false, false), - sender!.ObservableForProperty(property3Name, false, false), - sender!.ObservableForProperty(property4Name, false, false), - sender!.ObservableForProperty(property5Name, false, false), - sender!.ObservableForProperty(property6Name, false, false), - sender!.ObservableForProperty(property7Name, false, false), - sender!.ObservableForProperty(property8Name, false, false), - sender!.ObservableForProperty(property9Name, false, false), - sender!.ObservableForProperty(property10Name, false, false), - sender!.ObservableForProperty(property11Name, false, false), - sender!.ObservableForProperty(property12Name, false, false), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + string property11Name, + string property12Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false), + sender!.ObservableForProperty(property2Name, false, false), + sender!.ObservableForProperty(property3Name, false, false), + sender!.ObservableForProperty(property4Name, false, false), + sender!.ObservableForProperty(property5Name, false, false), + sender!.ObservableForProperty(property6Name, false, false), + sender!.ObservableForProperty(property7Name, false, false), + sender!.ObservableForProperty(property8Name, false, false), + sender!.ObservableForProperty(property9Name, false, false), + sender!.ObservableForProperty(property10Name, false, false), + sender!.ObservableForProperty(property11Name, false, false), + sender!.ObservableForProperty(property12Name, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// if set to true [is distinct]. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The 12 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - Expression> property1, - Expression> property2, - Expression> property3, - Expression> property4, - Expression> property5, - Expression> property6, - Expression> property7, - Expression> property8, - Expression> property9, - Expression> property10, - Expression> property11, - Expression> property12, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1, false, false, isDistinct), - sender!.ObservableForProperty(property2, false, false, isDistinct), - sender!.ObservableForProperty(property3, false, false, isDistinct), - sender!.ObservableForProperty(property4, false, false, isDistinct), - sender!.ObservableForProperty(property5, false, false, isDistinct), - sender!.ObservableForProperty(property6, false, false, isDistinct), - sender!.ObservableForProperty(property7, false, false, isDistinct), - sender!.ObservableForProperty(property8, false, false, isDistinct), - sender!.ObservableForProperty(property9, false, false, isDistinct), - sender!.ObservableForProperty(property10, false, false, isDistinct), - sender!.ObservableForProperty(property11, false, false, isDistinct), - sender!.ObservableForProperty(property12, false, false, isDistinct), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// if set to true [is distinct]. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The 12 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAny( + this TSender? sender, + Expression> property1, + Expression> property2, + Expression> property3, + Expression> property4, + Expression> property5, + Expression> property6, + Expression> property7, + Expression> property8, + Expression> property9, + Expression> property10, + Expression> property11, + Expression> property12, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1, false, false, isDistinct), + sender!.ObservableForProperty(property2, false, false, isDistinct), + sender!.ObservableForProperty(property3, false, false, isDistinct), + sender!.ObservableForProperty(property4, false, false, isDistinct), + sender!.ObservableForProperty(property5, false, false, isDistinct), + sender!.ObservableForProperty(property6, false, false, isDistinct), + sender!.ObservableForProperty(property7, false, false, isDistinct), + sender!.ObservableForProperty(property8, false, false, isDistinct), + sender!.ObservableForProperty(property9, false, false, isDistinct), + sender!.ObservableForProperty(property10, false, false, isDistinct), + sender!.ObservableForProperty(property11, false, false, isDistinct), + sender!.ObservableForProperty(property12, false, false, isDistinct), + selector + ); + } - /// - /// AOT-friendly WhenAny overload using property names and distinct option. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAny( - this TSender? sender, - string property1Name, - string property2Name, - string property3Name, - string property4Name, - string property5Name, - string property6Name, - string property7Name, - string property8Name, - string property9Name, - string property10Name, - string property11Name, - string property12Name, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - sender!.ObservableForProperty(property1Name, false, false, isDistinct), - sender!.ObservableForProperty(property2Name, false, false, isDistinct), - sender!.ObservableForProperty(property3Name, false, false, isDistinct), - sender!.ObservableForProperty(property4Name, false, false, isDistinct), - sender!.ObservableForProperty(property5Name, false, false, isDistinct), - sender!.ObservableForProperty(property6Name, false, false, isDistinct), - sender!.ObservableForProperty(property7Name, false, false, isDistinct), - sender!.ObservableForProperty(property8Name, false, false, isDistinct), - sender!.ObservableForProperty(property9Name, false, false, isDistinct), - sender!.ObservableForProperty(property10Name, false, false, isDistinct), - sender!.ObservableForProperty(property11Name, false, false, isDistinct), - sender!.ObservableForProperty(property12Name, false, false, isDistinct), - selector - ); - } + /// + /// AOT-friendly WhenAny overload using property names and distinct option. + /// + public static IObservable WhenAny( + this TSender? sender, + string property1Name, + string property2Name, + string property3Name, + string property4Name, + string property5Name, + string property6Name, + string property7Name, + string property8Name, + string property9Name, + string property10Name, + string property11Name, + string property12Name, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + sender!.ObservableForProperty(property1Name, false, false, isDistinct), + sender!.ObservableForProperty(property2Name, false, false, isDistinct), + sender!.ObservableForProperty(property3Name, false, false, isDistinct), + sender!.ObservableForProperty(property4Name, false, false, isDistinct), + sender!.ObservableForProperty(property5Name, false, false, isDistinct), + sender!.ObservableForProperty(property6Name, false, false, isDistinct), + sender!.ObservableForProperty(property7Name, false, false, isDistinct), + sender!.ObservableForProperty(property8Name, false, false, isDistinct), + sender!.ObservableForProperty(property9Name, false, false, isDistinct), + sender!.ObservableForProperty(property10Name, false, false, isDistinct), + sender!.ObservableForProperty(property11Name, false, false, isDistinct), + sender!.ObservableForProperty(property12Name, false, false, isDistinct), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The 12 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Expression? property9, - Expression? property10, - Expression? property11, - Expression? property12, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property9, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property10, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property11, false, false), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property12, false, false), - selector - ); - } + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The 12 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Expression? property9, + Expression? property10, + Expression? property11, + Expression? property12, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property9, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property10, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property11, false, false), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property12, false, false), + selector + ); + } - /// - /// WhenAny allows you to observe whenever one or more properties on an - /// object have changed, providing an initial value when the Observable - /// is set up, unlike ObservableForProperty(). Use this method in - /// constructors to set up bindings between properties that also need an - /// initial setup. - /// - /// The object where the property chain starts. - /// The 1 property chain to reference. This will be a expression pointing to a end property or field. - /// The 2 property chain to reference. This will be a expression pointing to a end property or field. - /// The 3 property chain to reference. This will be a expression pointing to a end property or field. - /// The 4 property chain to reference. This will be a expression pointing to a end property or field. - /// The 5 property chain to reference. This will be a expression pointing to a end property or field. - /// The 6 property chain to reference. This will be a expression pointing to a end property or field. - /// The 7 property chain to reference. This will be a expression pointing to a end property or field. - /// The 8 property chain to reference. This will be a expression pointing to a end property or field. - /// The 9 property chain to reference. This will be a expression pointing to a end property or field. - /// The 10 property chain to reference. This will be a expression pointing to a end property or field. - /// The 11 property chain to reference. This will be a expression pointing to a end property or field. - /// The 12 property chain to reference. This will be a expression pointing to a end property or field. - /// The selector which will determine the final value from the properties. - /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyDynamic( - this TSender? sender, - Expression? property1, - Expression? property2, - Expression? property3, - Expression? property4, - Expression? property5, - Expression? property6, - Expression? property7, - Expression? property8, - Expression? property9, - Expression? property10, - Expression? property11, - Expression? property12, - Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, - bool isDistinct) - { - return Observable.CombineLatest( - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property9, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property10, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property11, false, false, isDistinct), - ReactiveNotifyPropertyChangedMixin - .SubscribeToExpressionChain(sender, property12, false, false, isDistinct), - selector - ); - } -} + /// + /// WhenAny allows you to observe whenever one or more properties on an + /// object have changed, providing an initial value when the Observable + /// is set up, unlike ObservableForProperty(). Use this method in + /// constructors to set up bindings between properties that also need an + /// initial setup. + /// + /// The object where the property chain starts. + /// The 1 property chain to reference. This will be a expression pointing to a end property or field. + /// The 2 property chain to reference. This will be a expression pointing to a end property or field. + /// The 3 property chain to reference. This will be a expression pointing to a end property or field. + /// The 4 property chain to reference. This will be a expression pointing to a end property or field. + /// The 5 property chain to reference. This will be a expression pointing to a end property or field. + /// The 6 property chain to reference. This will be a expression pointing to a end property or field. + /// The 7 property chain to reference. This will be a expression pointing to a end property or field. + /// The 8 property chain to reference. This will be a expression pointing to a end property or field. + /// The 9 property chain to reference. This will be a expression pointing to a end property or field. + /// The 10 property chain to reference. This will be a expression pointing to a end property or field. + /// The 11 property chain to reference. This will be a expression pointing to a end property or field. + /// The 12 property chain to reference. This will be a expression pointing to a end property or field. + /// The selector which will determine the final value from the properties. + /// if set to true [is distinct]. + public static IObservable WhenAnyDynamic( + this TSender? sender, + Expression? property1, + Expression? property2, + Expression? property3, + Expression? property4, + Expression? property5, + Expression? property6, + Expression? property7, + Expression? property8, + Expression? property9, + Expression? property10, + Expression? property11, + Expression? property12, + Func, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, IObservedChange, TRet> selector, + bool isDistinct) + { + return Observable.CombineLatest( + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property1, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property2, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property3, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property4, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property5, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property6, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property7, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property8, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property9, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property10, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property11, false, false, isDistinct), + ReactiveNotifyPropertyChangedMixin + .SubscribeToExpressionChain(sender, property12, false, false, isDistinct), + selector + ); + } + } -/// A mixin which provides support for subscribing to observable properties. -public static class WhenAnyObservableMixin -{ - /// Observe a observable which is set to a property, and automatically subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The first observable to observe. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1) - where TSender : class - { - return sender.WhenAny(obs1, x => x.Value!.EmptyIfNull()).Switch(); - } + /// A mixin which provides support for subscribing to observable properties. + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] + public static class WhenAnyObservableMixin + { + /// Observe a observable which is set to a property, and automatically subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The first observable to observe. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1) + where TSender : class + { + return sender.WhenAny(obs1, x => x.Value!.EmptyIfNull()).Switch(); + } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2) - where TSender : class - { - return sender.WhenAny(obs1, obs2, (o1, o2) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, (o1, o2, o3) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, (o1, o2, o3, o4) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, (o1, o2, o3, o4, o5) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, (o1, o2, o3, o4, o5, o6) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, (o1, o2, o3, o4, o5, o6, o7) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, (o1, o2, o3, o4, o5, o6, o7, o8) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 9 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8, Expression?>> obs9) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, (o1, o2, o3, o4, o5, o6, o7, o8, o9) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 9 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 10 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8, Expression?>> obs9, Expression?>> obs10) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 9 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 10 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 11 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8, Expression?>> obs9, Expression?>> obs10, Expression?>> obs11) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, obs11, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), o11.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 9 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 10 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 11 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. - /// The 12 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8, Expression?>> obs9, Expression?>> obs10, Expression?>> obs11, Expression?>> obs12) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, obs11, obs12, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), o11.Value!.EmptyIfNull(), o12.Value!.EmptyIfNull()}) - .Select(x => x.Merge()).Switch(); - } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2) + where TSender : class + { + return sender.WhenAny(obs1, obs2, (o1, o2) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, (o1, o2, o3) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, (o1, o2, o3, o4) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, (o1, o2, o3, o4, o5) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, (o1, o2, o3, o4, o5, o6) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, (o1, o2, o3, o4, o5, o6, o7) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, (o1, o2, o3, o4, o5, o6, o7, o8) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 9 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8, Expression?>> obs9) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, (o1, o2, o3, o4, o5, o6, o7, o8, o9) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 9 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 10 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8, Expression?>> obs9, Expression?>> obs10) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 9 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 10 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 11 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8, Expression?>> obs9, Expression?>> obs10, Expression?>> obs11) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, obs11, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), o11.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 2 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 3 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 4 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 5 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 6 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 7 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 8 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 9 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 10 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 11 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + /// The 12 property chain to reference which ends with an observable. This will be a expression pointing to a end property or field which must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1, Expression?>> obs2, Expression?>> obs3, Expression?>> obs4, Expression?>> obs5, Expression?>> obs6, Expression?>> obs7, Expression?>> obs8, Expression?>> obs9, Expression?>> obs10, Expression?>> obs11, Expression?>> obs12) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, obs11, obs12, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12) => new[] {o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), o11.Value!.EmptyIfNull(), o12.Value!.EmptyIfNull()}) + .Select(x => x.Merge()).Switch(); + } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, (o1, o2) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, (o1, o2, o3) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The 4 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Expression?>> obs4, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, (o1, o2, o3, o4) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The 4 property chain to reference. - /// The 5 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Expression?>> obs4, - Expression?>> obs5, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, (o1, o2, o3, o4, o5) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The 4 property chain to reference. - /// The 5 property chain to reference. - /// The 6 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Expression?>> obs4, - Expression?>> obs5, - Expression?>> obs6, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, (o1, o2, o3, o4, o5, o6) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The 4 property chain to reference. - /// The 5 property chain to reference. - /// The 6 property chain to reference. - /// The 7 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Expression?>> obs4, - Expression?>> obs5, - Expression?>> obs6, - Expression?>> obs7, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, (o1, o2, o3, o4, o5, o6, o7) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The 4 property chain to reference. - /// The 5 property chain to reference. - /// The 6 property chain to reference. - /// The 7 property chain to reference. - /// The 8 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Expression?>> obs4, - Expression?>> obs5, - Expression?>> obs6, - Expression?>> obs7, - Expression?>> obs8, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, (o1, o2, o3, o4, o5, o6, o7, o8) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The 4 property chain to reference. - /// The 5 property chain to reference. - /// The 6 property chain to reference. - /// The 7 property chain to reference. - /// The 8 property chain to reference. - /// The 9 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Expression?>> obs4, - Expression?>> obs5, - Expression?>> obs6, - Expression?>> obs7, - Expression?>> obs8, - Expression?>> obs9, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, (o1, o2, o3, o4, o5, o6, o7, o8, o9) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The 4 property chain to reference. - /// The 5 property chain to reference. - /// The 6 property chain to reference. - /// The 7 property chain to reference. - /// The 8 property chain to reference. - /// The 9 property chain to reference. - /// The 10 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Expression?>> obs4, - Expression?>> obs5, - Expression?>> obs6, - Expression?>> obs7, - Expression?>> obs8, - Expression?>> obs9, - Expression?>> obs10, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The 4 property chain to reference. - /// The 5 property chain to reference. - /// The 6 property chain to reference. - /// The 7 property chain to reference. - /// The 8 property chain to reference. - /// The 9 property chain to reference. - /// The 10 property chain to reference. - /// The 11 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Expression?>> obs4, - Expression?>> obs5, - Expression?>> obs6, - Expression?>> obs7, - Expression?>> obs8, - Expression?>> obs9, - Expression?>> obs10, - Expression?>> obs11, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, obs11, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), o11.Value!.EmptyIfNull(), selector)) - .Switch(); - } - /// Monitor a property that is an observable, and subscribe to the most recent emitted value. - /// The object where the property chain starts. - /// The 1 property chain to reference. - /// The 2 property chain to reference. - /// The 3 property chain to reference. - /// The 4 property chain to reference. - /// The 5 property chain to reference. - /// The 6 property chain to reference. - /// The 7 property chain to reference. - /// The 8 property chain to reference. - /// The 9 property chain to reference. - /// The 10 property chain to reference. - /// The 11 property chain to reference. - /// The 12 property chain to reference. - /// The selector which will determine the final value from the properties. This must be an observable. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif - public static IObservable WhenAnyObservable(this TSender? sender, - Expression?>> obs1, - Expression?>> obs2, - Expression?>> obs3, - Expression?>> obs4, - Expression?>> obs5, - Expression?>> obs6, - Expression?>> obs7, - Expression?>> obs8, - Expression?>> obs9, - Expression?>> obs10, - Expression?>> obs11, - Expression?>> obs12, - Func selector) - where TSender : class - { - return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, obs11, obs12, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), o11.Value!.EmptyIfNull(), o12.Value!.EmptyIfNull(), selector)) - .Switch(); - } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, (o1, o2) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, (o1, o2, o3) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The 4 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Expression?>> obs4, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, (o1, o2, o3, o4) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The 4 property chain to reference. + /// The 5 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Expression?>> obs4, + Expression?>> obs5, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, (o1, o2, o3, o4, o5) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The 4 property chain to reference. + /// The 5 property chain to reference. + /// The 6 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Expression?>> obs4, + Expression?>> obs5, + Expression?>> obs6, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, (o1, o2, o3, o4, o5, o6) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The 4 property chain to reference. + /// The 5 property chain to reference. + /// The 6 property chain to reference. + /// The 7 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Expression?>> obs4, + Expression?>> obs5, + Expression?>> obs6, + Expression?>> obs7, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, (o1, o2, o3, o4, o5, o6, o7) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The 4 property chain to reference. + /// The 5 property chain to reference. + /// The 6 property chain to reference. + /// The 7 property chain to reference. + /// The 8 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Expression?>> obs4, + Expression?>> obs5, + Expression?>> obs6, + Expression?>> obs7, + Expression?>> obs8, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, (o1, o2, o3, o4, o5, o6, o7, o8) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The 4 property chain to reference. + /// The 5 property chain to reference. + /// The 6 property chain to reference. + /// The 7 property chain to reference. + /// The 8 property chain to reference. + /// The 9 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Expression?>> obs4, + Expression?>> obs5, + Expression?>> obs6, + Expression?>> obs7, + Expression?>> obs8, + Expression?>> obs9, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, (o1, o2, o3, o4, o5, o6, o7, o8, o9) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The 4 property chain to reference. + /// The 5 property chain to reference. + /// The 6 property chain to reference. + /// The 7 property chain to reference. + /// The 8 property chain to reference. + /// The 9 property chain to reference. + /// The 10 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Expression?>> obs4, + Expression?>> obs5, + Expression?>> obs6, + Expression?>> obs7, + Expression?>> obs8, + Expression?>> obs9, + Expression?>> obs10, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The 4 property chain to reference. + /// The 5 property chain to reference. + /// The 6 property chain to reference. + /// The 7 property chain to reference. + /// The 8 property chain to reference. + /// The 9 property chain to reference. + /// The 10 property chain to reference. + /// The 11 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Expression?>> obs4, + Expression?>> obs5, + Expression?>> obs6, + Expression?>> obs7, + Expression?>> obs8, + Expression?>> obs9, + Expression?>> obs10, + Expression?>> obs11, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, obs11, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), o11.Value!.EmptyIfNull(), selector)) + .Switch(); + } + /// Monitor a property that is an observable, and subscribe to the most recent emitted value. + /// The object where the property chain starts. + /// The 1 property chain to reference. + /// The 2 property chain to reference. + /// The 3 property chain to reference. + /// The 4 property chain to reference. + /// The 5 property chain to reference. + /// The 6 property chain to reference. + /// The 7 property chain to reference. + /// The 8 property chain to reference. + /// The 9 property chain to reference. + /// The 10 property chain to reference. + /// The 11 property chain to reference. + /// The 12 property chain to reference. + /// The selector which will determine the final value from the properties. This must be an observable. + public static IObservable WhenAnyObservable(this TSender? sender, + Expression?>> obs1, + Expression?>> obs2, + Expression?>> obs3, + Expression?>> obs4, + Expression?>> obs5, + Expression?>> obs6, + Expression?>> obs7, + Expression?>> obs8, + Expression?>> obs9, + Expression?>> obs10, + Expression?>> obs11, + Expression?>> obs12, + Func selector) + where TSender : class + { + return sender.WhenAny(obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10, obs11, obs12, (o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12) => Observable.CombineLatest(o1.Value!.EmptyIfNull(), o2.Value!.EmptyIfNull(), o3.Value!.EmptyIfNull(), o4.Value!.EmptyIfNull(), o5.Value!.EmptyIfNull(), o6.Value!.EmptyIfNull(), o7.Value!.EmptyIfNull(), o8.Value!.EmptyIfNull(), o9.Value!.EmptyIfNull(), o10.Value!.EmptyIfNull(), o11.Value!.EmptyIfNull(), o12.Value!.EmptyIfNull(), selector)) + .Switch(); + } } -internal static class ObservableExtensions -{ - public static IObservable EmptyIfNull(this IObservable @this) + internal static class ObservableExtensions { - return @this ?? Observable.Empty(); + public static IObservable EmptyIfNull(this IObservable @this) + { + return @this ?? Observable.Empty(); + } } -} \ No newline at end of file +} diff --git a/src/ReactiveUI/VariadicTemplates.tt b/src/ReactiveUI/VariadicTemplates.tt index fe5b365658..dcb37e2b88 100644 --- a/src/ReactiveUI/VariadicTemplates.tt +++ b/src/ReactiveUI/VariadicTemplates.tt @@ -35,6 +35,7 @@ int maxFuncLength = 12; namespace ReactiveUI { /// Extension methods associated with the WhenAny/WhenAnyValue classes. + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class WhenAnyMixin { /// @@ -46,10 +47,6 @@ namespace ReactiveUI /// /// The object where the property chain starts. /// The first property chain to reference. This will be a expression pointing to a end property or field. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyValue( this TSender? sender, Expression> property1) @@ -62,10 +59,6 @@ namespace ReactiveUI /// /// The object where the property chain starts. /// The property name to observe. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyValue( this TSender? sender, string propertyName) @@ -84,10 +77,6 @@ namespace ReactiveUI /// The object where the property chain starts. /// The first property chain to reference. This will be a expression pointing to a end property or field. /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyValue( this TSender? sender, Expression> property1, @@ -99,10 +88,6 @@ namespace ReactiveUI /// /// AOT-friendly overload that avoids expression trees by using a property name and distinct option. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyValue( this TSender? sender, string propertyName, @@ -133,10 +118,6 @@ namespace ReactiveUI <# for(int i=1; i <= length; i++) { #> /// The <#=i#> property chain to reference. This will be a expression pointing to a end property or field. <# } #> -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable<(<#= String.Join(",", templParams) #>)> WhenAnyValue>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -153,10 +134,6 @@ namespace ReactiveUI <# if (length != 1 && length <= 7) { #>/// /// AOT-friendly tuple overloads using property names instead of expressions. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable<(<#= String.Join(",", templParams) #>)> WhenAnyValue>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -188,10 +165,6 @@ namespace ReactiveUI <# for(int i=1; i <= length; i++) { #> /// The <#=i#> property chain to reference. This will be a expression pointing to a end property or field. <# } #> -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable<(<#= String.Join(",", templParams) #>)> WhenAnyValue>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -210,10 +183,6 @@ namespace ReactiveUI <# if (length != 1 && length <= 7) { #>/// /// AOT-friendly tuple overloads using property names with distinct option. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable<(<#= String.Join(",", templParams) #>)> WhenAnyValue>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -245,10 +214,6 @@ namespace ReactiveUI <# for(int i=1; i <= length; i++) { #> /// The <#=i#> property chain to reference. This will be a expression pointing to a end property or field. <# } #>/// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyValue>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -264,10 +229,6 @@ namespace ReactiveUI /// /// AOT-friendly selector overload using property names. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyValue>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -301,10 +262,6 @@ namespace ReactiveUI <# for(int i=1; i <= length; i++) { #> /// The <#=i#> property chain to reference. This will be a expression pointing to a end property or field. <# } #>/// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyValue>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -322,10 +279,6 @@ namespace ReactiveUI /// /// AOT-friendly selector overload using property names and distinct option. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyValue>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -357,12 +310,8 @@ namespace ReactiveUI /// /// The object where the property chain starts. <# for(int i=1; i <= length; i++) { #> -/// The <#=i#> property chain to reference. This will be a expression pointing to a end property or field. + /// The <#=i#> property chain to reference. This will be a expression pointing to a end property or field. <# } #>/// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAny>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -385,10 +334,6 @@ namespace ReactiveUI /// /// AOT-friendly WhenAny overload using property names. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAny>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -421,10 +366,6 @@ namespace ReactiveUI <# for(int i=1; i <= length; i++) { #> /// The <#=i#> property chain to reference. This will be a expression pointing to a end property or field. <# } #>/// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAny>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -448,10 +389,6 @@ namespace ReactiveUI /// /// AOT-friendly WhenAny overload using property names and distinct option. /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAny>( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -484,10 +421,6 @@ namespace ReactiveUI <# for(int i=1; i <= length; i++) { #> /// The <#=i#> property chain to reference. This will be a expression pointing to a end property or field. <# } #>/// The selector which will determine the final value from the properties. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyDynamic( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -521,10 +454,6 @@ namespace ReactiveUI /// The <#=i#> property chain to reference. This will be a expression pointing to a end property or field. <# } #>/// The selector which will determine the final value from the properties. /// if set to true [is distinct]. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyDynamic( this TSender? sender, <# for(int i=1; i <= length; i++) { #> @@ -550,15 +479,12 @@ namespace ReactiveUI } /// A mixin which provides support for subscribing to observable properties. + [RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class WhenAnyObservableMixin { /// Observe a observable which is set to a property, and automatically subscribe to the most recent emitted value. /// The object where the property chain starts. /// The first observable to observe. -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyObservable(this TSender? sender, Expression?>> obs1) where TSender : class { @@ -574,10 +500,6 @@ namespace ReactiveUI <# string paramsStr = String.Join(", ", Enumerable.Range(1, length).Select(x => "Expression?>> obs" + x.ToString())); #> <# string varsStr = String.Join(", ", Enumerable.Range(1, length).Select(x => "obs" + x.ToString())); #> <# string valsStr = String.Join(", ", Enumerable.Range(1, length).Select(x => "o" + x.ToString() + ".Value!.EmptyIfNull()")); #> -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyObservable(this TSender? sender, <#= paramsStr #>) where TSender : class { @@ -597,10 +519,6 @@ namespace ReactiveUI <# string varsStr = String.Join(", ", Enumerable.Range(1, length).Select(x => "obs" + x.ToString())); #> <# string valsStr = String.Join(", ", Enumerable.Range(1, length).Select(x => "o" + x.ToString() + ".Value!.EmptyIfNull()")); #> <# string selectorTypeParams = String.Join(", ", templParams); #> -#if NET6_0_OR_GREATER - [RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in AOT scenarios.")] - [RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] -#endif public static IObservable WhenAnyObservable>(this TSender? sender, <# for(int i=1; i <= length; i++) { #> Expression>?>> obs<#=i#>, diff --git a/src/ReactiveUI/View/DefaultViewLocator.AOT.cs b/src/ReactiveUI/View/DefaultViewLocator.AOT.cs deleted file mode 100644 index eac6f5b29e..0000000000 --- a/src/ReactiveUI/View/DefaultViewLocator.AOT.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2025 .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.Concurrent; - -namespace ReactiveUI; - -/// -/// Adds an AOT-friendly mapping configuration to DefaultViewLocator so callers can avoid reflection. -/// -public sealed partial class DefaultViewLocator -{ - // Keyed by (ViewModelType, Contract). Empty string represents default contract. - private readonly ConcurrentDictionary<(Type vmType, string contract), Func> _aotMappings = new(); - - /// - /// Registers a direct mapping from a view model type to a view factory. - /// This avoids reflection-based name lookup, improving AOT and trimming support. - /// - /// View model type. - /// View type. - /// Factory that builds the view. - /// Optional contract used to disambiguate views. - /// The locator for chaining. -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Mapping does not use reflection")] - [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Mapping does not use dynamic code")] -#endif - public DefaultViewLocator Map(Func factory, string? contract = null) - where TViewModel : class - where TView : class, IViewFor - { - ArgumentExceptionHelper.ThrowIfNull(factory); - _aotMappings[(typeof(TViewModel), contract ?? string.Empty)] = () => factory(); - return this; - } - - /// - /// Clears a previously registered mapping for an optional contract. - /// - /// View model type. - /// Optional contract to unmap. - /// The locator for chaining. - public DefaultViewLocator Unmap(string? contract = null) - where TViewModel : class - { - _ = _aotMappings.TryRemove((typeof(TViewModel), contract ?? string.Empty), out _); - return this; - } - - private IViewFor? TryResolveAOTMapping(Type viewModelType, string? contract) - { - // Try exact contract - if (_aotMappings.TryGetValue((viewModelType, contract ?? string.Empty), out var f)) - { - return f(); - } - - // Fallback to default contract if a specific contract was requested - if (!string.IsNullOrEmpty(contract) && _aotMappings.TryGetValue((viewModelType, string.Empty), out var fDefault)) - { - return fDefault(); - } - - return null; - } -} diff --git a/src/ReactiveUI/View/DefaultViewLocator.cs b/src/ReactiveUI/View/DefaultViewLocator.cs index e6487d385f..114aff8981 100644 --- a/src/ReactiveUI/View/DefaultViewLocator.cs +++ b/src/ReactiveUI/View/DefaultViewLocator.cs @@ -1,245 +1,214 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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.Concurrent; using System.Globalization; -using System.Reflection; namespace ReactiveUI; /// -/// Default implementation of that resolves views by convention (replacing "ViewModel" with "View"). +/// Default AOT-compatible implementation of that resolves views using compile-time registrations. /// /// /// -/// This locator queries Splat's service locator for a registered using several fallbacks, including -/// the exact view type, IViewFor<TViewModel>, and interface/class naming conversions. Override -/// to customize the name translation strategy. +/// This locator uses explicit view-to-viewmodel mappings registered via . +/// When no mapping is found, it falls back to querying the service locator for IViewFor<TViewModel>. +/// +/// +/// This implementation is fully AOT-compatible and does not use reflection or runtime type discovery. +/// All view-viewmodel associations must be registered at application startup. /// /// /// /// /// new LoginView(), typeof(IViewFor)); -/// +/// // Register views at startup /// var locator = new DefaultViewLocator(); -/// var view = locator.ResolveView(new LoginViewModel()); +/// locator.Map(() => new LoginView()) +/// .Map(() => new MainView()) +/// .Map(() => new SettingsView()); +/// +/// // Resolve at runtime (fully AOT-compatible) +/// var view = locator.ResolveView(); /// ]]> /// /// -public sealed partial class DefaultViewLocator : IViewLocator +public sealed class DefaultViewLocator : IViewLocator { - /// - /// Initializes a new instance of the class. - /// - /// Custom mapping from view model type name to view type name. - internal DefaultViewLocator(Func? viewModelToViewFunc = null) => - ViewModelToViewFunc = viewModelToViewFunc ?? (static vm => vm.Replace("ViewModel", "View")); + // Lock object for synchronizing writes to _mappings. +#if NET9_0_OR_GREATER + private readonly System.Threading.Lock _gate = new(); +#else + private readonly object _gate = new(); +#endif + + // Cache for MakeGenericType calls in ResolveView(object). + // Key: ViewModelType, Value: IViewFor interface type. + private readonly ConcurrentDictionary _viewForTypeCache = new(); + + // Snapshot pattern: Readers access this volatile reference without locking. + // Writers lock, clone, mutate, and swap the reference. + // Keyed by (ViewModelType, Contract). Empty string represents default contract. + private Dictionary<(Type ViewModelType, string Contract), Func> _mappings = new(); /// - /// Gets or sets the function used to convert a view model type name into a view type name during resolution. + /// Initializes a new instance of the class. /// - /// - /// The view model to view function. - /// - public Func ViewModelToViewFunc { get; set; } + internal DefaultViewLocator() + { + } /// - /// Returns the view associated with a view model, deriving the name of the type via , then discovering it via the - /// service locator. + /// Registers a direct mapping from a view model type to a view factory. + /// This is the recommended way to register views for AOT-compatible applications. /// - /// The type. - /// - /// - /// Given view model type T with runtime type RT, this implementation will attempt to resolve the following views: - /// - /// - /// - /// Look for a service registered under the type whose name is given to us by passing RT to (which defaults to changing "ViewModel" to "View"). - /// - /// - /// - /// - /// Look for a service registered under the type IViewFor<RT>. - /// - /// - /// - /// - /// Look for a service registered under the type whose name is given to us by passing T to (which defaults to changing "ViewModel" to "View"). - /// - /// - /// - /// - /// Look for a service registered under the type IViewFor<T>. - /// - /// - /// - /// - /// If T is an interface, change its name to that of a class (i.e. drop the leading "I"). If it's a class, change to an interface (i.e. add a leading "I"). - /// - /// - /// - /// - /// Repeat steps 1-4 with the type resolved from the modified name. - /// - /// - /// - /// - /// - /// - /// The view model whose associated view is to be resolved. - /// - /// - /// Optional contract to be used when resolving from Splat. - /// - /// - /// The view associated with the given view model. - /// -#if NET6_0_OR_GREATER - [RequiresDynamicCode("View resolution uses reflection and type discovery")] - [RequiresUnreferencedCode("View resolution may reference types that could be trimmed")] -#endif - public IViewFor? ResolveView(T? viewModel, string? contract = null) + /// View model type. + /// View type that implements IViewFor<TViewModel>. + /// Factory function that creates the view instance. + /// Optional contract used to disambiguate multiple views for the same view model. + /// The locator for chaining. + /// + /// + /// (() => new LoginView()) + /// .Map(() => new MainView()); + /// ]]> + /// + /// + public DefaultViewLocator Map(Func factory, string? contract = null) + where TViewModel : class + where TView : class, IViewFor { - ArgumentExceptionHelper.ThrowIfNull(viewModel); + ArgumentExceptionHelper.ThrowIfNull(factory); - var mapped = TryResolveAOTMapping(viewModel!.GetType(), contract); - if (mapped is not null) - { - return mapped; - } - - var view = AttemptViewResolutionFor(viewModel!.GetType(), contract) - ?? AttemptViewResolutionFor(typeof(T), contract) - ?? AttemptViewResolutionFor(ToggleViewModelType(viewModel.GetType()), contract) - ?? AttemptViewResolutionFor(ToggleViewModelType(typeof(T)), contract); + var key = (typeof(TViewModel), contract ?? string.Empty); - if (view is not null) + lock (_gate) { - return view; + var current = Volatile.Read(ref _mappings); + var newMappings = new Dictionary<(Type, string), Func>(current) + { + // Covariance of delegates allows Func to be assigned to Func + [key] = factory + }; + + Interlocked.Exchange(ref _mappings, newMappings); } - this.Log().Warn(CultureInfo.InvariantCulture, "Failed to resolve view for view model type '{0}'.", typeof(T).FullName); - return null; + return this; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Type resolution requires dynamic code generation")] - [RequiresUnreferencedCode("Type resolution may reference types that could be trimmed")] -#endif - private static Type? ToggleViewModelType(Type viewModelType) + /// + /// Removes a previously registered view mapping. + /// + /// View model type to unmap. + /// Optional contract to unmap. If null, removes the default mapping. + /// The locator for chaining. + public DefaultViewLocator Unmap(string? contract = null) + where TViewModel : class { - var viewModelTypeName = viewModelType.AssemblyQualifiedName; - - if (viewModelTypeName is null) - { - return null; - } + var key = (typeof(TViewModel), contract ?? string.Empty); - if (viewModelType.GetTypeInfo().IsInterface) + lock (_gate) { -#if NET6_0_OR_GREATER - if (viewModelType.Name.StartsWith('I')) -#else - if (viewModelType.Name.StartsWith("I", StringComparison.InvariantCulture)) -#endif + var current = Volatile.Read(ref _mappings); + if (current.ContainsKey(key)) { - var toggledTypeName = DeinterfaceifyTypeName(viewModelTypeName); - return Reflection.ReallyFindType(toggledTypeName, throwOnFailure: false); + var newMappings = new Dictionary<(Type, string), Func>(current); + newMappings.Remove(key); + Interlocked.Exchange(ref _mappings, newMappings); } } - else - { - var toggledTypeName = InterfaceifyTypeName(viewModelTypeName); - return Reflection.ReallyFindType(toggledTypeName, throwOnFailure: false); - } - return null; + return this; } - private static string DeinterfaceifyTypeName(string typeName) - { - var idxComma = typeName.IndexOf(',', 0); - var idxPeriod = typeName.LastIndexOf('.', idxComma - 1); -#if NET6_0_OR_GREATER - return string.Concat(typeName.AsSpan(0, idxPeriod + 1), typeName.AsSpan(idxPeriod + 2)); -#else - return typeName.Substring(0, idxPeriod + 1) + typeName.Substring(idxPeriod + 2); -#endif - } - - private static string InterfaceifyTypeName(string typeName) - { - var idxComma = typeName.IndexOf(',', 0); - var idxPeriod = typeName.LastIndexOf('.', idxComma - 1); - return typeName.Insert(idxPeriod + 1, "I"); - } - -#if NET6_0_OR_GREATER - [RequiresDynamicCode("View resolution uses reflection and type discovery")] - [RequiresUnreferencedCode("View resolution may reference types that could be trimmed")] -#endif - private IViewFor? AttemptViewResolutionFor(Type? viewModelType, string? contract) + /// + /// Resolves a view for a view model type known at compile time. Fully AOT-compatible. + /// + /// The view model type to resolve a view for. + /// Optional contract to disambiguate between multiple views for the same view model. + /// The resolved view or when no registration is available. + /// + /// + /// Resolution strategy: + /// + /// Check for explicit mapping registered via with the specified contract. + /// Query the service locator for IViewFor<TViewModel> with the specified contract. + /// + /// + /// + public IViewFor? ResolveView(string? contract = null) + where TViewModel : class { - if (viewModelType is null) - { - return null; - } - - var viewModelTypeName = viewModelType.AssemblyQualifiedName; - - if (viewModelTypeName is null) + // Snapshot read - lock-free + // Volatile.Read ensures we get the latest published dictionary reference. + // The dictionary itself is immutable (copy-on-write), so no further locking is needed. + var mappings = Volatile.Read(ref _mappings); + var vmType = typeof(TViewModel); + var contractKey = contract ?? string.Empty; + + // 1. Check explicit AOT mappings + if (mappings.TryGetValue((vmType, contractKey), out var factory)) { - return null; + this.Log().Debug(CultureInfo.InvariantCulture, "Resolved IViewFor<{0}> from explicit mapping", typeof(TViewModel).Name); + return (IViewFor)factory(); } - var proposedViewTypeName = ViewModelToViewFunc(viewModelTypeName); - var view = AttemptViewResolution(proposedViewTypeName, contract); - + // 2. Fallback to service locator + var view = AppLocator.Current?.GetService>(contract); if (view is not null) { + this.Log().Debug(CultureInfo.InvariantCulture, "Resolved IViewFor<{0}> via service locator", typeof(TViewModel).Name); return view; } - proposedViewTypeName = typeof(IViewFor<>).MakeGenericType(viewModelType).AssemblyQualifiedName; - return AttemptViewResolution(proposedViewTypeName, contract); + this.Log().Warn(CultureInfo.InvariantCulture, "Failed to resolve view for {0}. Use Map() or register IViewFor in the service locator.", typeof(TViewModel).Name); + return null; } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("View resolution uses reflection and type discovery")] - [RequiresUnreferencedCode("View resolution may reference types that could be trimmed")] -#endif - private IViewFor? AttemptViewResolution(string? viewTypeName, string? contract) + /// + [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 = null) { - try + if (instance is null) { - var viewType = Reflection.ReallyFindType(viewTypeName, throwOnFailure: false); - if (viewType is null) - { - return null; - } + return null; + } - var service = AppLocator.Current?.GetService(viewType, contract); + var vmType = instance.GetType(); + var contractKey = contract ?? string.Empty; - if (service is null) - { - return null; - } + // Snapshot read - lock-free + var mappings = Volatile.Read(ref _mappings); - if (service is not IViewFor view) + // 1) Explicit AOT mappings first (fastest, no reflection beyond GetType and Dictionary lookup) + if (mappings.TryGetValue((vmType, contractKey), out var factory)) + { + var view = factory(); + if (view is { } viewFor) { - return null; + viewFor.ViewModel = instance; + return viewFor; } - this.Log().Debug(CultureInfo.InvariantCulture, "Resolved service type '{0}'", viewType.FullName); - - return view; + return null; } - catch (Exception ex) + + // 2) Fallback to service locator via runtime-constructed service type. + // We cache the MakeGenericType result to avoid reflection overhead on subsequent calls. + var serviceType = _viewForTypeCache.GetOrAdd(vmType, static t => typeof(IViewFor<>).MakeGenericType(t)); + + var resolved = AppLocator.Current.GetService(serviceType, contract); + if (resolved is IViewFor resolvedViewFor) { - this.Log().Error(ex, $"Exception occurred whilst attempting to resolve type {viewTypeName} into a view."); - throw; + resolvedViewFor.ViewModel = instance; + return resolvedViewFor; } + + return null; } } diff --git a/src/ReactiveUI/View/ViewMappingBuilder.cs b/src/ReactiveUI/View/ViewMappingBuilder.cs new file mode 100644 index 0000000000..7b44d83fef --- /dev/null +++ b/src/ReactiveUI/View/ViewMappingBuilder.cs @@ -0,0 +1,76 @@ +// Copyright (c) 2025 .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; + +/// +/// Fluent builder for registering AOT-compatible view-to-viewmodel mappings. +/// +public sealed class ViewMappingBuilder +{ + private readonly DefaultViewLocator _locator; + + /// + /// Initializes a new instance of the class. + /// + /// The view locator to register mappings with. + internal ViewMappingBuilder(DefaultViewLocator locator) + { + ArgumentExceptionHelper.ThrowIfNull(locator); + _locator = locator; + } + + /// + /// Maps a view model type to a view type with automatic instantiation. + /// The view must have a parameterless constructor. + /// + /// The view model type. + /// The view type implementing IViewFor<TViewModel>. + /// Optional contract to disambiguate multiple views for the same view model. + /// The builder for chaining. + public ViewMappingBuilder Map(string? contract = null) + where TViewModel : class + where TView : class, IViewFor, new() + { + _locator.Map(() => new TView(), contract); + return this; + } + + /// + /// Maps a view model type to a view type with a custom factory function. + /// Use this when the view requires constructor parameters or custom initialization. + /// + /// The view model type. + /// The view type implementing IViewFor<TViewModel>. + /// Factory function that creates the view. + /// Optional contract to disambiguate multiple views for the same view model. + /// The builder for chaining. + public ViewMappingBuilder Map(Func factory, string? contract = null) + where TViewModel : class + where TView : class, IViewFor + { + ArgumentExceptionHelper.ThrowIfNull(factory); + _locator.Map(factory, contract); + return this; + } + + /// + /// Maps a view model type to a view resolved from the service locator. + /// The view must be registered in the dependency injection container. + /// + /// The view model type. + /// The view type implementing IViewFor<TViewModel>. + /// Optional contract to disambiguate multiple views for the same view model. + /// The builder for chaining. + public ViewMappingBuilder MapFromServiceLocator(string? contract = null) + where TViewModel : class + where TView : class, IViewFor + { + _locator.Map( + () => AppLocator.Current.GetService() ?? throw new InvalidOperationException($"View {typeof(TView).Name} not registered in service locator"), + contract); + return this; + } +} diff --git a/src/RxUI.DotSettings b/src/RxUI.DotSettings deleted file mode 100644 index 314a4a12e0..0000000000 --- a/src/RxUI.DotSettings +++ /dev/null @@ -1,30 +0,0 @@ - - True - True - True - True - True - True - True - True - END_OF_LINE - False - False - False - END_OF_LINE - NEVER - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> - <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Private Fields/Methods/Properties"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /><Kind Name="CONSTANT_FIELD" /><Kind Name="METHOD" /><Kind Name="PROPERTY" /><Kind Name="EVENT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> - <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> - True - True - True - True - True - True - True - True - True - True \ No newline at end of file diff --git a/src/examples/ReactiveUI.Builder.WpfApp/App.xaml.cs b/src/examples/ReactiveUI.Builder.WpfApp/App.xaml.cs index f19f6ba186..1bb3998849 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/App.xaml.cs +++ b/src/examples/ReactiveUI.Builder.WpfApp/App.xaml.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Reactive; using System.Reactive.Linq; using System.Windows; @@ -40,6 +41,17 @@ protected override void OnStartup(StartupEventArgs e) ////.RegisterView() ////.RegisterView() ////.RegisterView() + .WithSuspensionHost() // Configure typed suspension host + .WithCacheSizes(smallCacheLimit: 100, bigCacheLimit: 400) // Customize cache sizes + .WithExceptionHandler(Observer.Create(static ex => + { + // Custom exception handler - log unhandled reactive errors + Trace.WriteLine($"[ReactiveUI] Unhandled exception: {ex}"); + if (Debugger.IsAttached) + { + Debugger.Break(); + } + })) .WithRegistration(static r => { // Register IScreen as a singleton so all resolutions share the same Router @@ -60,7 +72,7 @@ protected override void OnStartup(StartupEventArgs e) .BuildApp(); // Setup Suspension - RxApp.SuspensionHost.CreateNewAppState = static () => new ChatState(); + RxSuspension.SuspensionHost.CreateNewAppState = static () => new ChatState(); var statePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @@ -71,7 +83,7 @@ protected override void OnStartup(StartupEventArgs e) _driver = new Services.FileJsonSuspensionDriver(statePath); // Set an initial state instantly to avoid blocking UI - RxApp.SuspensionHost.AppState = new ChatState(); + RxSuspension.SuspensionHost.AppState = new ChatState(); // Load persisted state asynchronously and update UI when ready _ = _driver @@ -80,7 +92,7 @@ protected override void OnStartup(StartupEventArgs e) .Subscribe( static stateObj => { - RxApp.SuspensionHost.AppState = stateObj; + RxSuspension.SuspensionHost.AppState = stateObj; MessageBus.Current.SendMessage(new ChatStateChanged()); Trace.WriteLine("[App] State loaded"); }, @@ -125,9 +137,9 @@ protected override void OnExit(ExitEventArgs e) Trace.WriteLine($"[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 && RxApp.SuspensionHost.AppState is not null) + if (remaining == 0 && _driver is not null && RxSuspension.SuspensionHost.AppState is not null) { - _driver.SaveState(RxApp.SuspensionHost.AppState).Wait(); + _driver.SaveState(RxSuspension.SuspensionHost.AppState).Wait(); } } finally diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs b/src/examples/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs index 85a29a8ddd..57e107b3f0 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs +++ b/src/examples/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs @@ -50,13 +50,13 @@ public ChatNetworkService() // Outgoing chat messages (default contract) - only send messages originating from this instance MessageBus.Current.Listen() .Where(static m => m.InstanceId == AppInstance.Id) - .ObserveOn(RxApp.TaskpoolScheduler) + .ObserveOn(RxSchedulers.TaskpoolScheduler) .Subscribe(Send); // Outgoing room events - only send messages originating from this instance MessageBus.Current.Listen(contract: RoomsContract) .Where(static m => m.InstanceId == AppInstance.Id) - .ObserveOn(RxApp.TaskpoolScheduler) + .ObserveOn(RxSchedulers.TaskpoolScheduler) .Subscribe(Send); Trace.WriteLine("[Net] ChatNetworkService initialized."); diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs b/src/examples/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs index 49cab743f5..08f329951b 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs +++ b/src/examples/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs @@ -7,6 +7,7 @@ using System.Reactive; using System.Reactive.Linq; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using ReactiveUI.Builder.WpfApp.Models; @@ -38,7 +39,7 @@ public IObservable InvalidateState() => Observable.Start( File.Delete(path); } }, - RxApp.TaskpoolScheduler); + RxSchedulers.TaskpoolScheduler); /// /// Loads the application state from persistent storage. @@ -46,7 +47,7 @@ public IObservable InvalidateState() => Observable.Start( /// /// An object observable. /// - public IObservable LoadState() => Observable.Start( + public IObservable LoadState() => Observable.Start( () => { if (!File.Exists(path)) @@ -57,20 +58,55 @@ public IObservable LoadState() => Observable.Start( var json = File.ReadAllText(path); return JsonSerializer.Deserialize(json) ?? new ChatState(); }, - RxApp.TaskpoolScheduler); + RxSchedulers.TaskpoolScheduler); + + /// + /// Loads the application state from persistent storage using source-generated JSON metadata. + /// + /// The type of state to load. + /// The source-generated JSON type info. + /// An observable that produces the deserialized state. + public IObservable LoadState(JsonTypeInfo typeInfo) => Observable.Start( + () => + { + if (!File.Exists(path)) + { + return default(T); + } + + var json = File.ReadAllText(path); + return JsonSerializer.Deserialize(json, typeInfo); + }, + RxSchedulers.TaskpoolScheduler); /// /// Saves the application state to disk. /// + /// The type of state to save. /// The application state. /// /// A completed observable. /// - public IObservable SaveState(object state) => Observable.Start( + public IObservable SaveState(T state) => Observable.Start( () => { var json = JsonSerializer.Serialize(state, _options); File.WriteAllText(path, json); }, - RxApp.TaskpoolScheduler); + RxSchedulers.TaskpoolScheduler); + + /// + /// Saves the application state to disk using source-generated JSON metadata. + /// + /// The type of state to save. + /// The application state. + /// The source-generated JSON type info. + /// A completed observable. + public IObservable SaveState(T state, JsonTypeInfo typeInfo) => Observable.Start( + () => + { + var json = JsonSerializer.Serialize(state, typeInfo); + File.WriteAllText(path, json); + }, + RxSchedulers.TaskpoolScheduler); } diff --git a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/LobbyViewModel.cs b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/LobbyViewModel.cs index 5c9fa7e2fc..1c5b7e9e78 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/LobbyViewModel.cs +++ b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/LobbyViewModel.cs @@ -71,7 +71,7 @@ public LobbyViewModel(IScreen hostScreen) .Select(_ => Unit.Default); RoomsChanged = localRoomsChanged.Merge(remoteRoomsChanged) - .Throttle(TimeSpan.FromMilliseconds(50), RxApp.TaskpoolScheduler); + .Throttle(TimeSpan.FromMilliseconds(50), RxSchedulers.TaskpoolScheduler); this.WhenAnyObservable(x => x.RoomsChanged) .StartWith(Unit.Default) @@ -138,7 +138,7 @@ public string RoomName /// public ReactiveCommand JoinRoom { get; } - private static ChatState GetState() => RxApp.SuspensionHost.GetAppState(); + private static ChatState GetState() => RxSuspension.SuspensionHost.GetAppState(); private static void ApplyRoomEvent(Services.RoomEventMessage evt) { diff --git a/src/reactiveui.slnx b/src/reactiveui.slnx new file mode 100644 index 0000000000..0d0d9a97ca --- /dev/null +++ b/src/reactiveui.slnx @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/testconfig.json b/src/testconfig.json index 2982c9cadd..aa7a31ab23 100644 --- a/src/testconfig.json +++ b/src/testconfig.json @@ -1,20 +1,25 @@ { - "platform": { - "execution": { - "parallel": false - } - }, - "extensions": [ - { - "extensionId": "Microsoft.Testing.Extensions.CodeCoverage", - "settings": { - "format": "cobertura", - "skipAutoProperties": true, - "modulePaths": { - "include": [ "ReactiveUI\\..*" ], - "exclude": [ ".*Tests.*", ".*TestRunner.*" ] + "platform": { + "execution": { + "parallel": false } - } - } - ] + }, + "extensions": [ + { + "extensionId": "Microsoft.Testing.Extensions.CodeCoverage", + "settings": { + "format": "cobertura", + "skipAutoProperties": true, + "modulePaths": { + "include": [ + "ReactiveUI\\..*" + ], + "exclude": [ + ".*Tests.*", + ".*TestRunner.*" + ] + } + } + } + ] } diff --git a/src/tests/.editorconfig b/src/tests/.editorconfig index bb88305f32..a0c0b0520a 100644 --- a/src/tests/.editorconfig +++ b/src/tests/.editorconfig @@ -3,13 +3,13 @@ ################### # StyleCop Analyzers (SA) - Documentation Rules ################### -dotnet_diagnostic.SA1600.severity = none # Elements must be documented +dotnet_diagnostic.SA1600.severity = none # Elements must be documented ################### # Code Analysis (CA) - Disable rules for test projects ################### -dotnet_diagnostic.CA1001.severity = none # Owns disposable fields -dotnet_diagnostic.CA1063.severity = none # Implement IDisposable correctly -dotnet_diagnostic.CA1065.severity = none # Do not raise exceptions in unexpected locations -dotnet_diagnostic.CA1812.severity = none # Avoid uninstantiated internal classes -dotnet_diagnostic.CA2213.severity = none # Disposable fields should be disposed +dotnet_diagnostic.CA1001.severity = none # Owns disposable fields +dotnet_diagnostic.CA1063.severity = none # Implement IDisposable correctly +dotnet_diagnostic.CA1065.severity = none # Do not raise exceptions in unexpected locations +dotnet_diagnostic.CA1812.severity = none # Avoid uninstantiated internal classes +dotnet_diagnostic.CA2213.severity = none # Disposable fields should be disposed diff --git a/src/tests/ReactiveUI.AOTTests/AOTCompatibilityTests.cs b/src/tests/ReactiveUI.AOTTests/AOTCompatibilityTests.cs index 14b6f19a59..35c8931150 100644 --- a/src/tests/ReactiveUI.AOTTests/AOTCompatibilityTests.cs +++ b/src/tests/ReactiveUI.AOTTests/AOTCompatibilityTests.cs @@ -3,7 +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. -namespace ReactiveUI.AOTTests; +namespace ReactiveUI.AOT.Tests; /// /// Provides a suite of tests to verify compatibility of ReactiveUI features with ahead-of-time (AOT) compilation @@ -36,8 +36,6 @@ public async Task ReactiveObject_PropertyChanges_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing AOT-incompatible ReactiveCommand.Create method")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing AOT-incompatible ReactiveCommand.Create method")] public async Task ReactiveCommand_Create_WorksInAOT() { var executed = false; @@ -53,8 +51,6 @@ public async Task ReactiveCommand_Create_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing AOT-incompatible ReactiveCommand.Create method")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing AOT-incompatible ReactiveCommand.Create method")] public async Task ReactiveCommand_CreateWithParameter_WorksInAOT() { string? result = null; @@ -70,8 +66,6 @@ public async Task ReactiveCommand_CreateWithParameter_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ToProperty with string-based property names which requires AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ToProperty with string-based property names which requires AOT suppression")] public async Task ObservableAsPropertyHelper_WorksInAOT() { var obj = new TestReactiveObject(); @@ -88,8 +82,6 @@ public async Task ObservableAsPropertyHelper_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing WhenAnyValue which requires AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing WhenAnyValue which requires AOT suppression")] public async Task WhenAnyValue_StringPropertyNames_WorksInAOT() { var obj = new TestReactiveObject(); @@ -153,8 +145,6 @@ public async Task INPCPropertyObservation_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing AOT-incompatible ReactiveCommand.CreateFromObservable method")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing AOT-incompatible ReactiveCommand.CreateFromObservable method")] public async Task ReactiveCommand_CreateFromObservable_WorksInAOT() { var command = ReactiveCommand.CreateFromObservable(() => Observable.Return(42)); @@ -170,8 +160,6 @@ public async Task ReactiveCommand_CreateFromObservable_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ToProperty with string-based property names which requires AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ToProperty with string-based property names which requires AOT suppression")] public async Task StringBasedPropertyBinding_WorksInAOT() { var obj = new TestReactiveObject(); diff --git a/src/tests/ReactiveUI.AOTTests/AdvancedAOTTests.cs b/src/tests/ReactiveUI.AOTTests/AdvancedAOTTests.cs index e39fa5ddfc..2ad5ce8378 100644 --- a/src/tests/ReactiveUI.AOTTests/AdvancedAOTTests.cs +++ b/src/tests/ReactiveUI.AOTTests/AdvancedAOTTests.cs @@ -7,13 +7,18 @@ using System.Reactive.Disposables; using System.Reactive.Subjects; -namespace ReactiveUI.AOTTests; +using ReactiveUI.Tests.Utilities.AppBuilder; + +using TUnit.Core.Executors; + +namespace ReactiveUI.AOT.Tests; /// /// Provides a suite of advanced tests to verify that key ReactiveUI features function correctly under Ahead-of-Time /// (AOT) compilation scenarios. /// [NotInParallel] // These tests modify global state (e.g., Locator.Current) +[TestExecutor] public class AdvancedAOTTests { /// @@ -21,8 +26,6 @@ public class AdvancedAOTTests /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing AOT-incompatible RoutingState which uses ReactiveCommand")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing AOT-incompatible RoutingState which uses ReactiveCommand")] public async Task RoutingState_Navigation_WorksInAOT() { var routingState = new RoutingState(); @@ -40,8 +43,6 @@ public async Task RoutingState_Navigation_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty constructor that uses RxApp")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty constructor that uses RxApp")] public async Task PropertyValidation_WorksInAOT() { var property = new ReactiveProperty(string.Empty, ImmediateScheduler.Instance, false, false); @@ -61,8 +62,6 @@ public async Task PropertyValidation_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty constructor that uses RxApp")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty constructor that uses RxApp")] public async Task ViewModelActivation_WorksInAOT() { var viewModel = new TestActivatableViewModel(); @@ -87,8 +86,6 @@ public async Task ViewModelActivation_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ToProperty which requires AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ToProperty which requires AOT suppression")] public async Task ObservableAsPropertyHelper_Lifecycle_WorksInAOT() { var testObject = new TestReactiveObject(); diff --git a/src/tests/ReactiveUI.AOTTests/AssemblyHooks.cs b/src/tests/ReactiveUI.AOTTests/AssemblyHooks.cs index ae255b33b9..a25602ab84 100644 --- a/src/tests/ReactiveUI.AOTTests/AssemblyHooks.cs +++ b/src/tests/ReactiveUI.AOTTests/AssemblyHooks.cs @@ -3,10 +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; -using TUnit.Core; +using ReactiveUI.Builder; +using ReactiveUI.Tests.Utilities.AppBuilder; -namespace ReactiveUI.AOTTests; +using TUnit.Core.Executors; + +[assembly: TestExecutor] +[assembly: NotInParallel] + +namespace ReactiveUI.AOT.Tests; /// /// Assembly-level hooks for test initialization and cleanup. @@ -21,6 +26,10 @@ public static void AssemblySetup() { // Override ModeDetector to ensure we're detected as being in a unit test runner ModeDetector.OverrideModeDetector(new TestModeDetector()); + + RxAppBuilder.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); } /// diff --git a/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTMarkupTests.cs b/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTMarkupTests.cs index e1cb306b8f..1bdeda7abc 100644 --- a/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTMarkupTests.cs +++ b/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTMarkupTests.cs @@ -7,7 +7,7 @@ using System.Reactive.Disposables; using System.Reactive.Subjects; -namespace ReactiveUI.AOTTests; +namespace ReactiveUI.AOT.Tests; /// /// Tests for testing the AOT and making sure trimming is correct. @@ -20,8 +20,6 @@ public class ComprehensiveAOTMarkupTests /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveObject AOT-incompatible constructor")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveObject AOT-incompatible constructor")] public async Task ReactiveObject_Constructor_WorksWithAOTSuppression() { var obj = new TestReactiveObject(); @@ -42,8 +40,6 @@ public async Task ReactiveObject_Constructor_WorksWithAOTSuppression() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty Refresh AOT-incompatible method")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty Refresh AOT-incompatible method")] public async Task ReactiveProperty_Refresh_WorksWithAOTSuppression() { var scheduler = CurrentThreadScheduler.Instance; @@ -66,8 +62,6 @@ public async Task ReactiveProperty_Refresh_WorksWithAOTSuppression() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty which requires AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty which requires AOT suppression")] public async Task PlatformSpecific_AOTMarkup_IsProperlyApplied() { // This test validates that platform-specific code has AOT attributes @@ -87,8 +81,6 @@ public async Task PlatformSpecific_AOTMarkup_IsProperlyApplied() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing comprehensive ReactiveProperty AOT scenarios")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing comprehensive ReactiveProperty AOT scenarios")] public async Task ReactiveProperty_ComprehensiveOperations_WorkWithAOT() { var scheduler = CurrentThreadScheduler.Instance; @@ -126,8 +118,6 @@ public async Task ReactiveProperty_ComprehensiveOperations_WorkWithAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing mixed AOT scenario")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing mixed AOT scenario")] public async Task MixedAOTScenario_ComplexWorkflow_WorksCorrectly() { var scheduler = CurrentThreadScheduler.Instance; @@ -172,8 +162,6 @@ public async Task MixedAOTScenario_ComplexWorkflow_WorksCorrectly() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ToProperty which requires AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ToProperty which requires AOT suppression")] public async Task ObservableAsPropertyHelper_AOTCompatibleUsage_Works() { var obj = new TestReactiveObject(); @@ -199,8 +187,6 @@ public async Task ObservableAsPropertyHelper_AOTCompatibleUsage_Works() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] public async Task DependencyInjection_AOTCompatiblePatterns_Work() { var resolver = Locator.CurrentMutable; @@ -239,8 +225,6 @@ public async Task DependencyInjection_AOTCompatiblePatterns_Work() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] public async Task ViewModelActivation_AOTCompatible_WorksCorrectly() { var viewModel = new TestActivatableViewModel(); @@ -296,8 +280,6 @@ public async Task ViewModelActivation_AOTCompatible_WorksCorrectly() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing error handling in AOT scenario")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing error handling in AOT scenario")] public async Task ErrorHandling_AOTScenarios_WorkCorrectly() { var scheduler = CurrentThreadScheduler.Instance; diff --git a/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTTests.cs b/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTTests.cs index 9edcfd8581..82a6bab5af 100644 --- a/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTTests.cs +++ b/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTTests.cs @@ -7,7 +7,7 @@ using System.Reactive.Disposables; using System.Reactive.Subjects; -namespace ReactiveUI.AOTTests; +namespace ReactiveUI.AOT.Tests; /// /// Provides a suite of tests that verify AOT-compatible patterns and usage scenarios for reactive programming @@ -23,8 +23,6 @@ public class ComprehensiveAOTTests /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ToProperty with string-based property names which requires AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ToProperty with string-based property names which requires AOT suppression")] public async Task AOTCompatiblePatterns_WorkCorrectly() { // Use string-based property names for ToProperty (AOT-compatible) @@ -93,8 +91,6 @@ public async Task MessageBus_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Demonstrating proper suppression of AOT warnings for ReactiveCommand")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Demonstrating proper suppression of AOT warnings for ReactiveCommand")] public async Task ReactiveCommand_WithProperSuppression_WorksInAOT() { var executed = false; @@ -115,8 +111,6 @@ public async Task ReactiveCommand_WithProperSuppression_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty with explicit scheduler which requires AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty with explicit scheduler which requires AOT suppression")] public async Task ReactiveProperty_WithExplicitScheduler_WorksInAOT() { // Use explicit scheduler to avoid RxApp dependency (AOT-friendly) @@ -141,8 +135,6 @@ public async Task ReactiveProperty_WithExplicitScheduler_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ToProperty with string-based property names which requires AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ToProperty with string-based property names which requires AOT suppression")] public async Task ObservableAsPropertyHelper_StringBased_WorksInAOT() { var obj = new TestReactiveObject(); @@ -190,8 +182,6 @@ public async Task DependencyInjection_BasicUsage_WorksInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty constructor that uses RxApp")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty constructor that uses RxApp")] public async Task ViewModelActivation_PatternWorks_InAOT() { var viewModel = new TestActivatableViewModel(); diff --git a/src/tests/ReactiveUI.AOTTests/FinalAOTValidationTests.cs b/src/tests/ReactiveUI.AOTTests/FinalAOTValidationTests.cs index 816a67ebaa..df3d6f82bb 100644 --- a/src/tests/ReactiveUI.AOTTests/FinalAOTValidationTests.cs +++ b/src/tests/ReactiveUI.AOTTests/FinalAOTValidationTests.cs @@ -8,7 +8,7 @@ using System.Reactive.Disposables; using System.Reactive.Subjects; -namespace ReactiveUI.AOTTests; +namespace ReactiveUI.AOT.Tests; /// /// Provides a suite of tests that validate the compatibility of key ReactiveUI patterns and features with Ahead-of-Time @@ -21,8 +21,6 @@ public class FinalAOTValidationTests /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] public async Task CompleteAOTCompatibleWorkflow_WorksSeamlessly() { // 1. Create objects using AOT-compatible patterns @@ -72,8 +70,6 @@ public async Task CompleteAOTCompatibleWorkflow_WorksSeamlessly() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Demonstrating ReactiveCommand usage with proper AOT suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Demonstrating ReactiveCommand usage with proper AOT suppression")] public async Task ReactiveCommand_CompleteWorkflow_WorksWithSuppression() { // Use CurrentThreadScheduler to ensure synchronous execution @@ -122,8 +118,6 @@ public async Task ReactiveCommand_CompleteWorkflow_WorksWithSuppression() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing mixed AOT scenario with ReactiveCommand")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing mixed AOT scenario with ReactiveCommand")] public async Task MixedAOTScenario_ComplexApplication_Works() { var scheduler = CurrentThreadScheduler.Instance; @@ -169,8 +163,6 @@ public async Task MixedAOTScenario_ComplexApplication_Works() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] public async Task DependencyInjection_AdvancedScenarios_WorkInAOT() { var resolver = Locator.CurrentMutable; @@ -200,8 +192,6 @@ public async Task DependencyInjection_AdvancedScenarios_WorkInAOT() /// /// A representing the asynchronous operation. [Test] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Testing ReactiveProperty in AOT scenario with proper suppression")] public async Task ErrorHandlingAndDisposal_PatternsWork_InAOT() { var disposables = new CompositeDisposable(); diff --git a/src/tests/ReactiveUI.AOTTests/ReactiveUI.AOT.Tests.csproj b/src/tests/ReactiveUI.AOTTests/ReactiveUI.AOT.Tests.csproj index e9bb3fbc0f..ecf6a6d64f 100644 --- a/src/tests/ReactiveUI.AOTTests/ReactiveUI.AOT.Tests.csproj +++ b/src/tests/ReactiveUI.AOTTests/ReactiveUI.AOT.Tests.csproj @@ -16,6 +16,7 @@ + diff --git a/src/tests/ReactiveUI.AOTTests/StringBasedObservationTests.cs b/src/tests/ReactiveUI.AOTTests/StringBasedObservationTests.cs index 65a4c16bd7..fe5b543c9f 100644 --- a/src/tests/ReactiveUI.AOTTests/StringBasedObservationTests.cs +++ b/src/tests/ReactiveUI.AOTTests/StringBasedObservationTests.cs @@ -3,11 +3,16 @@ // 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.AOTTests; +using ReactiveUI.Tests.Utilities.AppBuilder; + +using TUnit.Core.Executors; + +namespace ReactiveUI.AOT.Tests; /// /// Observable string based observation tests. /// +[TestExecutor] public class StringBasedObservationTests { /// diff --git a/src/tests/ReactiveUI.AOTTests/StringBasedSemanticsTests.cs b/src/tests/ReactiveUI.AOTTests/StringBasedSemanticsTests.cs index 2e22237e93..c3999064a4 100644 --- a/src/tests/ReactiveUI.AOTTests/StringBasedSemanticsTests.cs +++ b/src/tests/ReactiveUI.AOTTests/StringBasedSemanticsTests.cs @@ -3,12 +3,17 @@ // 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.AOTTests; +using ReactiveUI.Tests.Utilities.AppBuilder; + +using TUnit.Core.Executors; + +namespace ReactiveUI.AOT.Tests; /// /// Contains unit tests that verify the behavior of string-based property observation and change notification mechanisms /// in reactive objects. /// +[TestExecutor] public class StringBasedSemanticsTests { /// diff --git a/src/tests/ReactiveUI.AOTTests/TestActivatableViewModel.cs b/src/tests/ReactiveUI.AOTTests/TestActivatableViewModel.cs index 594bc9b107..5a6384df14 100644 --- a/src/tests/ReactiveUI.AOTTests/TestActivatableViewModel.cs +++ b/src/tests/ReactiveUI.AOTTests/TestActivatableViewModel.cs @@ -3,7 +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. -namespace ReactiveUI.AOTTests; +namespace ReactiveUI.AOT.Tests; /// /// Test activatable view model for AOT testing. diff --git a/src/tests/ReactiveUI.AOTTests/TestReactiveObject.cs b/src/tests/ReactiveUI.AOTTests/TestReactiveObject.cs index 50247cbe92..1385e3e92a 100644 --- a/src/tests/ReactiveUI.AOTTests/TestReactiveObject.cs +++ b/src/tests/ReactiveUI.AOTTests/TestReactiveObject.cs @@ -3,7 +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. -namespace ReactiveUI.AOTTests; +namespace ReactiveUI.AOT.Tests; /// /// Test ReactiveObject for AOT compatibility testing. @@ -16,8 +16,6 @@ public class TestReactiveObject : ReactiveObject /// /// Initializes a new instance of the class. /// - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "AOT compatibility tests deliberately use AOT-incompatible methods to test suppression scenarios")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "AOT compatibility tests deliberately use AOT-incompatible methods to test suppression scenarios")] public TestReactiveObject() { _computedProperty = this.WhenAnyValue(static x => x.TestProperty) @@ -31,8 +29,6 @@ public TestReactiveObject() public string? TestProperty { get => _testProperty; - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "AOT compatibility tests deliberately use AOT-incompatible methods to test suppression scenarios")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "AOT compatibility tests deliberately use AOT-incompatible methods to test suppression scenarios")] set => this.RaiseAndSetIfChanged(ref _testProperty, value); } diff --git a/src/tests/ReactiveUI.AOTTests/TestRoutableViewModel.cs b/src/tests/ReactiveUI.AOTTests/TestRoutableViewModel.cs index 83b0bb9b7b..f35962180f 100644 --- a/src/tests/ReactiveUI.AOTTests/TestRoutableViewModel.cs +++ b/src/tests/ReactiveUI.AOTTests/TestRoutableViewModel.cs @@ -3,7 +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. -namespace ReactiveUI.AOTTests; +namespace ReactiveUI.AOT.Tests; /// /// Test routable view model for AOT testing. diff --git a/src/tests/ReactiveUI.AOTTests/ViewLocatorAOTMappingTests.cs b/src/tests/ReactiveUI.AOTTests/ViewLocatorAOTMappingTests.cs index c2a6296b59..b978396f01 100644 --- a/src/tests/ReactiveUI.AOTTests/ViewLocatorAOTMappingTests.cs +++ b/src/tests/ReactiveUI.AOTTests/ViewLocatorAOTMappingTests.cs @@ -3,21 +3,18 @@ // 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.AOTTests; +using ReactiveUI.Tests.Utilities.AppBuilder; + +using TUnit.Core.Executors; + +namespace ReactiveUI.AOT.Tests; /// /// Tests for ViewLocator AOT mappings. /// +[TestExecutor] public class ViewLocatorAOTMappingTests { - /// - /// Initializes a new instance of the class. - /// - public ViewLocatorAOTMappingTests() - { - RxApp.EnsureInitialized(); - } - /// /// Map/Resolve with contract and default fallback works. /// @@ -31,17 +28,15 @@ public async Task Map_ResolveView_UsesAOTMappingWithContract() locator.Map(static () => new ViewA(), contract: "mobile") .Map(static () => new ViewADefault()); // default - var vm = new VmA(); - - var viewMobile = locator.ResolveView(vm, "mobile"); + var viewMobile = locator.ResolveView("mobile"); await Assert.That(viewMobile).IsTypeOf(); - var viewDefaultFromExplicit = locator.ResolveView(vm, string.Empty); + var viewDefaultFromExplicit = locator.ResolveView(string.Empty); await Assert.That(viewDefaultFromExplicit).IsTypeOf(); - // Unknown contract falls back to default mapping - var viewFallback = locator.ResolveView(vm, "unknown"); - await Assert.That(viewFallback).IsTypeOf(); + // Unknown contract returns null (no fallback in ViewLocator) + var viewUnknown = locator.ResolveView("unknown"); + await Assert.That(viewUnknown).IsNull(); } /// @@ -54,11 +49,10 @@ public async Task Unmap_RemovesMapping() var locator = new DefaultViewLocator(); locator.Map(static () => new ViewB(), contract: "c1"); - var vm = new VmB(); - await Assert.That(locator.ResolveView(vm, "c1")).IsTypeOf(); + await Assert.That(locator.ResolveView("c1")).IsTypeOf(); locator.Unmap("c1"); - await Assert.That(locator.ResolveView(vm, "c1")).IsNull(); + await Assert.That(locator.ResolveView("c1")).IsNull(); } /// @@ -71,8 +65,7 @@ public async Task Map_ResolveView_UsesAOTMappingWithoutContract() var locator = new DefaultViewLocator(); locator.Map(static () => new ViewA()); - var vm = new VmA(); - var view = locator.ResolveView(vm); + var view = locator.ResolveView(); await Assert.That(view).IsNotNull(); await Assert.That(view).IsTypeOf(); diff --git a/src/tests/ReactiveUI.Blazor.Tests/BlazorReactiveUIBuilderExtensionsTests.cs b/src/tests/ReactiveUI.Blazor.Tests/BlazorReactiveUIBuilderExtensionsTests.cs index fe18fe9ffb..c5b60ae4de 100644 --- a/src/tests/ReactiveUI.Blazor.Tests/BlazorReactiveUIBuilderExtensionsTests.cs +++ b/src/tests/ReactiveUI.Blazor.Tests/BlazorReactiveUIBuilderExtensionsTests.cs @@ -167,6 +167,8 @@ public Splat.Builder.IAppBuilder UsingModule(T registrationModule) public IReactiveUIBuilder ConfigureMessageBus(Action configure) => throw new NotImplementedException(); + public IReactiveUIBuilder WithMessageBus(IMessageBus messageBus) => throw new NotImplementedException(); + public IReactiveUIBuilder ConfigureSuspensionDriver(Action configure) => throw new NotImplementedException(); public IReactiveUIBuilder ConfigureViewLocator(Action configure) => throw new NotImplementedException(); @@ -235,5 +237,13 @@ public IReactiveUIBuilder UsingSplatModule(T registrationModule) public IReactiveUIInstance WithInstance(Action action) => throw new NotImplementedException(); public IReactiveUIInstance WithInstance(Action action) => throw new NotImplementedException(); + + public IReactiveUIBuilder WithExceptionHandler(IObserver exceptionHandler) => throw new NotImplementedException(); + + public IReactiveUIBuilder WithSuspensionHost() => throw new NotImplementedException(); + + public IReactiveUIBuilder WithSuspensionHost() => throw new NotImplementedException(); + + public IReactiveUIBuilder WithCacheSizes(int smallCacheLimit, int bigCacheLimit) => throw new NotImplementedException(); } } diff --git a/src/tests/ReactiveUI.Blazor.Tests/ReactiveUI.Blazor.Tests.csproj b/src/tests/ReactiveUI.Blazor.Tests/ReactiveUI.Blazor.Tests.csproj index b76cbb017a..3ffd2f3ec5 100644 --- a/src/tests/ReactiveUI.Blazor.Tests/ReactiveUI.Blazor.Tests.csproj +++ b/src/tests/ReactiveUI.Blazor.Tests/ReactiveUI.Blazor.Tests.csproj @@ -16,10 +16,11 @@ - + + @@ -27,4 +28,4 @@ - \ No newline at end of file + diff --git a/src/tests/ReactiveUI.Builder.Maui.Tests/Activation/ActivationForViewFetcherTests.cs b/src/tests/ReactiveUI.Builder.Maui.Tests/Activation/ActivationForViewFetcherTests.cs index 1a1b41da00..907be5632f 100644 --- a/src/tests/ReactiveUI.Builder.Maui.Tests/Activation/ActivationForViewFetcherTests.cs +++ b/src/tests/ReactiveUI.Builder.Maui.Tests/Activation/ActivationForViewFetcherTests.cs @@ -29,7 +29,9 @@ public async Task PageAndChildViewActivateAndDeactivate() AppBuilder.ResetBuilderStateForTests(); var resolver = new ModernDependencyResolver(); resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); + resolver.CreateReactiveUIBuilder() + .WithPlatformServices() + .BuildApp(); resolver.RegisterConstant(new ActivationForViewFetcher()); using (resolver.WithResolver()) diff --git a/src/tests/ReactiveUI.Builder.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Builder.Tests/AssemblyHooks.cs index ea18bd34ef..2fd3f71290 100644 --- a/src/tests/ReactiveUI.Builder.Tests/AssemblyHooks.cs +++ b/src/tests/ReactiveUI.Builder.Tests/AssemblyHooks.cs @@ -21,6 +21,11 @@ public static void AssemblySetup() { // Override ModeDetector to ensure we're detected as being in a unit test runner ModeDetector.OverrideModeDetector(new TestModeDetector()); + + // Initialize ReactiveUI with core services + RxAppBuilder.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); } /// diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsHappyPathTests.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsHappyPathTests.cs index 3a99a94945..434680d321 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsHappyPathTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsHappyPathTests.cs @@ -3,12 +3,9 @@ // 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.Reactive.Concurrency; -using Splat.Builder; - -namespace ReactiveUI.Builder.Tests; +namespace ReactiveUI.Builder.Tests.Mixins; public class BuilderInstanceMixinsHappyPathTests { diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullActionTests.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullActionTests.cs index 430861020a..21837392dd 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullActionTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullActionTests.cs @@ -5,7 +5,7 @@ using Splat.Builder; -namespace ReactiveUI.Builder.Tests; +namespace ReactiveUI.Builder.Tests.Mixins; public class BuilderInstanceMixinsNullActionTests { diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullInstanceTests.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullInstanceTests.cs index acd1f72d6f..60238bf0ed 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullInstanceTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullInstanceTests.cs @@ -3,9 +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 Splat.Builder; - -namespace ReactiveUI.Builder.Tests; +namespace ReactiveUI.Builder.Tests.Mixins; public class BuilderInstanceMixinsNullInstanceTests { diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.cs index 28ff3acf13..3a26e3e80f 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.cs @@ -5,7 +5,7 @@ using Splat.Builder; -namespace ReactiveUI.Builder.Tests; +namespace ReactiveUI.Builder.Tests.Mixins; public class BuilderInstanceMixinsTests { diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderMixinsTests.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderMixinsTests.cs index baf0fa428d..8ab8f42ff8 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderMixinsTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderMixinsTests.cs @@ -5,10 +5,10 @@ using System.Reactive; using System.Reactive.Concurrency; - +using System.Text.Json.Serialization.Metadata; using Splat.Builder; -namespace ReactiveUI.Builder.Tests; +namespace ReactiveUI.Builder.Tests.Mixins; [NotInParallel] public class BuilderMixinsTests @@ -36,8 +36,7 @@ static BuilderMixinsTests() () => BuilderMixins.RegisterViewModel(null!), () => BuilderMixins.RegisterSingletonViewModel(null!), () => BuilderMixins.RegisterView(null!), - () => BuilderMixins.RegisterSingletonView(null!), - ]; + () => BuilderMixins.RegisterSingletonView(null!)]; } [Before(HookType.Test)] @@ -371,9 +370,9 @@ private sealed class SplatModuleMarker private sealed class TestRegistrationModule : IWantsToRegisterStuff { - public void Register(Action, Type> registerFunction) + public void Register(IRegistrar registrar) { - registerFunction(() => new PlatformRegistrationMarker(), typeof(PlatformRegistrationMarker)); + registrar.RegisterConstant(() => new PlatformRegistrationMarker()); } } @@ -395,7 +394,11 @@ private sealed class TestSuspensionDriver : ISuspensionDriver { public IObservable LoadState() => Observable.Return(null); - public IObservable SaveState(object state) => Observable.Return(Unit.Default); + public IObservable SaveState(T state) => Observable.Return(Unit.Default); + + public IObservable LoadState(JsonTypeInfo typeInfo) => Observable.Return(default); + + public IObservable SaveState(T state, JsonTypeInfo typeInfo) => Observable.Return(Unit.Default); public IObservable InvalidateState() => Observable.Return(Unit.Default); } diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderSchedulerMixinsTests.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderSchedulerMixinsTests.cs index aba1806788..b898a961df 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderSchedulerMixinsTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderSchedulerMixinsTests.cs @@ -4,10 +4,9 @@ // See the LICENSE file in the project root for full license information. using System.Reactive.Concurrency; - using Splat.Builder; -namespace ReactiveUI.Builder.Tests; +namespace ReactiveUI.Builder.Tests.Mixins; [NotInParallel] public class BuilderSchedulerMixinsTests diff --git a/src/tests/ReactiveUI.Builder.Tests/ReactiveUI.Builder.Tests.csproj b/src/tests/ReactiveUI.Builder.Tests/ReactiveUI.Builder.Tests.csproj index e285eb6524..00ab10ef18 100644 --- a/src/tests/ReactiveUI.Builder.Tests/ReactiveUI.Builder.Tests.csproj +++ b/src/tests/ReactiveUI.Builder.Tests/ReactiveUI.Builder.Tests.csproj @@ -9,10 +9,6 @@ false $(NoWarn);SA1600;CA1812; - - true - true - @@ -29,36 +25,6 @@ - - - - true - true - - - - - - - - - - - true - true - - - - - - - - - - - - - diff --git a/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderBlockingTests.cs b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderBlockingTests.cs index 2bcaf9ad11..ef90065b10 100644 --- a/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderBlockingTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderBlockingTests.cs @@ -17,9 +17,7 @@ public async Task Build_SetsFlag_AndBlocks_InitializeReactiveUI() using var locator = new ModernDependencyResolver(); var builder = locator.CreateReactiveUIBuilder(); - builder.WithCoreServices().Build(); - - locator.InitializeReactiveUI(); + builder.WithCoreServices().BuildApp(); var observableProperty = locator.GetService(); await Assert.That(observableProperty).IsNotNull(); diff --git a/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderRxAppMigrationTests.cs b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderRxAppMigrationTests.cs new file mode 100644 index 0000000000..0b7552a74f --- /dev/null +++ b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderRxAppMigrationTests.cs @@ -0,0 +1,266 @@ +// Copyright (c) 2025 .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.Reactive; +using Splat.Builder; + +namespace ReactiveUI.Builder.Tests; + +/// +/// Tests for RxApp migration functionality including WithExceptionHandler, WithSuspensionHost, and WithCacheSizes. +/// +[NotInParallel] +public class ReactiveUIBuilderRxAppMigrationTests +{ + /// + /// Resets ReactiveUI static state before each test. + /// + [Before(Test)] + public void SetUp() + { + RxAppBuilder.ResetForTesting(); + RxCacheSize.ResetForTesting(); + RxState.ResetForTesting(); + RxSuspension.ResetForTesting(); + } + + [Test] + public async Task WithExceptionHandler_Should_Set_Custom_Exception_Handler() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + Exception? capturedEx = null; + var customHandler = Observer.Create(ex => capturedEx = ex); + + locator.CreateReactiveUIBuilder() + .WithExceptionHandler(customHandler) + .WithCoreServices() + .BuildApp(); + + var testException = new InvalidOperationException("Test exception"); + RxState.DefaultExceptionHandler.OnNext(testException); + + await Assert.That(capturedEx).IsEqualTo(testException); + } + + [Test] + public void WithExceptionHandler_With_Null_Handler_Should_Throw() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + var builder = locator.CreateReactiveUIBuilder(); + + Assert.Throws(() => builder.WithExceptionHandler(null!)); + } + + [Test] + public async Task WithSuspensionHost_NonGeneric_Should_Create_Default_Host() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + locator.CreateReactiveUIBuilder() + .WithSuspensionHost() + .WithCoreServices() + .BuildApp(); + + var host = RxSuspension.SuspensionHost; + await Assert.That(host).IsNotNull(); + await Assert.That(host).IsTypeOf(); + } + + [Test] + public async Task WithSuspensionHost_Generic_Should_Create_Typed_Host() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + locator.CreateReactiveUIBuilder() + .WithSuspensionHost() + .WithCoreServices() + .BuildApp(); + + var host = RxSuspension.SuspensionHost; + await Assert.That(host).IsNotNull(); + await Assert.That(host).IsTypeOf>(); + } + + [Test] + public async Task WithCacheSizes_Should_Set_Custom_Cache_Sizes() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + locator.CreateReactiveUIBuilder() + .WithCacheSizes(smallCacheLimit: 128, bigCacheLimit: 512) + .WithCoreServices() + .BuildApp(); + + await Assert.That(RxCacheSize.SmallCacheLimit).IsEqualTo(128); + await Assert.That(RxCacheSize.BigCacheLimit).IsEqualTo(512); + } + + [Test] + public void WithCacheSizes_With_Zero_Or_Negative_Values_Should_Throw() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + var builder = locator.CreateReactiveUIBuilder(); + + Assert.Throws(() => + builder.WithCacheSizes(smallCacheLimit: 0, bigCacheLimit: 100)); + + Assert.Throws(() => + builder.WithCacheSizes(smallCacheLimit: 100, bigCacheLimit: 0)); + + Assert.Throws(() => + builder.WithCacheSizes(smallCacheLimit: -1, bigCacheLimit: 100)); + + Assert.Throws(() => + builder.WithCacheSizes(smallCacheLimit: 100, bigCacheLimit: -1)); + } + + [Test] + public async Task RxCacheSize_Should_Use_Platform_Defaults_When_Not_Configured() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + locator.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); + +#if ANDROID || IOS + await Assert.That(RxCacheSize.SmallCacheLimit).IsEqualTo(32); + await Assert.That(RxCacheSize.BigCacheLimit).IsEqualTo(64); +#else + await Assert.That(RxCacheSize.SmallCacheLimit).IsEqualTo(64); + await Assert.That(RxCacheSize.BigCacheLimit).IsEqualTo(256); +#endif + } + + [Test] + public async Task Builder_Should_Support_Chaining_All_RxApp_Migration_Methods() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + Exception? capturedEx = null; + var customHandler = Observer.Create(ex => capturedEx = ex); + + locator.CreateReactiveUIBuilder() + .WithExceptionHandler(customHandler) + .WithSuspensionHost() + .WithCacheSizes(smallCacheLimit: 100, bigCacheLimit: 400) + .WithCoreServices() + .BuildApp(); + + // Verify exception handler + var testException = new InvalidOperationException("Test"); + RxState.DefaultExceptionHandler.OnNext(testException); + await Assert.That(capturedEx).IsEqualTo(testException); + + // Verify suspension host + var host = RxSuspension.SuspensionHost; + await Assert.That(host).IsTypeOf>(); + + // Verify cache sizes + await Assert.That(RxCacheSize.SmallCacheLimit).IsEqualTo(100); + await Assert.That(RxCacheSize.BigCacheLimit).IsEqualTo(400); + } + + [Test] + public async Task RxSchedulers_DefaultExceptionHandler_Should_Not_Be_Null() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + locator.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); + + var handler = RxState.DefaultExceptionHandler; + await Assert.That(handler).IsNotNull(); + } + + [Test] + public async Task RxSchedulers_SuspensionHost_Should_Not_Be_Null() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + locator.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); + + var host = RxSuspension.SuspensionHost; + await Assert.That(host).IsNotNull(); + } + + [Test] + public async Task WithExceptionHandler_Called_Multiple_Times_Should_Use_Last_Handler() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + Exception? firstCaptured = null; + Exception? secondCaptured = null; + + var firstHandler = Observer.Create(ex => firstCaptured = ex); + var secondHandler = Observer.Create(ex => secondCaptured = ex); + + locator.CreateReactiveUIBuilder() + .WithExceptionHandler(firstHandler) + .WithExceptionHandler(secondHandler) + .WithCoreServices() + .BuildApp(); + + var testException = new InvalidOperationException("Test"); + RxState.DefaultExceptionHandler.OnNext(testException); + + await Assert.That(secondCaptured).IsEqualTo(testException); + await Assert.That(firstCaptured).IsNull(); + } + + [Test] + public async Task WithCacheSizes_Called_Multiple_Times_Should_Use_Last_Values() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + locator.CreateReactiveUIBuilder() + .WithCacheSizes(smallCacheLimit: 100, bigCacheLimit: 200) + .WithCacheSizes(smallCacheLimit: 300, bigCacheLimit: 600) + .WithCoreServices() + .BuildApp(); + + await Assert.That(RxCacheSize.SmallCacheLimit).IsEqualTo(300); + await Assert.That(RxCacheSize.BigCacheLimit).IsEqualTo(600); + } + + [Test] + public async Task WithSuspensionHost_Generic_Overrides_NonGeneric() + { + AppBuilder.ResetBuilderStateForTests(); + using var locator = new ModernDependencyResolver(); + + locator.CreateReactiveUIBuilder() + .WithSuspensionHost() + .WithSuspensionHost() + .WithCoreServices() + .BuildApp(); + + var host = RxSuspension.SuspensionHost; + await Assert.That(host).IsTypeOf>(); + } + + private class TestAppState + { + public string? Name { get; set; } + + public int Counter { get; set; } + } +} diff --git a/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTest.cs b/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTest.cs index 50af374641..4ebe9b861b 100644 --- a/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTest.cs @@ -3,12 +3,9 @@ // 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.Reactive; using System.Reactive.Subjects; -using Microsoft.Maui.Controls; -namespace ReactiveUI.Tests.Maui; +namespace ReactiveUI.Maui.Tests; /// /// Tests for . diff --git a/src/tests/ReactiveUI.NonParallel.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Maui.Tests/AssemblyHooks.cs similarity index 82% rename from src/tests/ReactiveUI.NonParallel.Tests/AssemblyHooks.cs rename to src/tests/ReactiveUI.Maui.Tests/AssemblyHooks.cs index 3d0c6f0311..ef25f60801 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/AssemblyHooks.cs +++ b/src/tests/ReactiveUI.Maui.Tests/AssemblyHooks.cs @@ -4,9 +4,10 @@ // See the LICENSE file in the project root for full license information. using System; +using ReactiveUI.Builder; using TUnit.Core; -namespace ReactiveUI.Tests.Core; +namespace ReactiveUI.Maui.Tests; /// /// Assembly-level hooks for test initialization and cleanup. @@ -16,17 +17,20 @@ public static class AssemblyHooks /// /// Called before any tests in this assembly start. /// - [Before(Assembly)] + [Before(HookType.Assembly)] public static void AssemblySetup() { // Override ModeDetector to ensure we're detected as being in a unit test runner ModeDetector.OverrideModeDetector(new TestModeDetector()); + + // Note: Individual tests will initialize ReactiveUI with their own schedulers + // via RxAppBuilder.WithMauiScheduler().BuildApp() } /// /// Called after all tests in this assembly complete. /// - [After(Assembly)] + [After(HookType.Assembly)] public static void AssemblyTeardown() { // Clean up resources diff --git a/src/tests/ReactiveUI.Maui.Tests/AutoSuspendHelperTest.cs b/src/tests/ReactiveUI.Maui.Tests/AutoSuspendHelperTest.cs index bc02bf079d..340942a401 100644 --- a/src/tests/ReactiveUI.Maui.Tests/AutoSuspendHelperTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/AutoSuspendHelperTest.cs @@ -3,7 +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. -namespace ReactiveUI.Tests.Maui; +namespace ReactiveUI.Maui.Tests; /// /// Tests for . @@ -31,11 +31,11 @@ public async Task Constructor_WiresUpSuspensionHost() { var helper = new AutoSuspendHelper(); - await Assert.That(RxApp.SuspensionHost.IsLaunchingNew).IsNotNull(); - await Assert.That(RxApp.SuspensionHost.IsUnpausing).IsNotNull(); - await Assert.That(RxApp.SuspensionHost.IsResuming).IsNotNull(); - await Assert.That(RxApp.SuspensionHost.ShouldPersistState).IsNotNull(); - await Assert.That(RxApp.SuspensionHost.ShouldInvalidateState).IsNotNull(); + await Assert.That(RxSuspension.SuspensionHost.IsLaunchingNew).IsNotNull(); + await Assert.That(RxSuspension.SuspensionHost.IsUnpausing).IsNotNull(); + await Assert.That(RxSuspension.SuspensionHost.IsResuming).IsNotNull(); + await Assert.That(RxSuspension.SuspensionHost.ShouldPersistState).IsNotNull(); + await Assert.That(RxSuspension.SuspensionHost.ShouldInvalidateState).IsNotNull(); } /// @@ -48,7 +48,7 @@ public async Task OnCreate_TriggersIsLaunchingNew() var helper = new AutoSuspendHelper(); var triggered = false; - RxApp.SuspensionHost.IsLaunchingNew.Subscribe(_ => triggered = true); + RxSuspension.SuspensionHost.IsLaunchingNew.Subscribe(_ => triggered = true); helper.OnCreate(); await Assert.That(triggered).IsTrue(); @@ -64,7 +64,7 @@ public async Task OnStart_TriggersIsUnpausing() var helper = new AutoSuspendHelper(); var triggered = false; - RxApp.SuspensionHost.IsUnpausing.Subscribe(_ => triggered = true); + RxSuspension.SuspensionHost.IsUnpausing.Subscribe(_ => triggered = true); helper.OnStart(); await Assert.That(triggered).IsTrue(); @@ -80,7 +80,7 @@ public async Task OnResume_TriggersIsResuming() var helper = new AutoSuspendHelper(); var triggered = false; - RxApp.SuspensionHost.IsResuming.Subscribe(_ => triggered = true); + RxSuspension.SuspensionHost.IsResuming.Subscribe(_ => triggered = true); helper.OnResume(); await Assert.That(triggered).IsTrue(); @@ -96,7 +96,7 @@ public async Task OnSleep_TriggersShouldPersistState() var helper = new AutoSuspendHelper(); var triggered = false; - RxApp.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/BooleanToVisibilityTypeConverterTest.cs b/src/tests/ReactiveUI.Maui.Tests/BooleanToVisibilityTypeConverterTest.cs index 9323c9a90a..4ee175a67c 100644 --- a/src/tests/ReactiveUI.Maui.Tests/BooleanToVisibilityTypeConverterTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/BooleanToVisibilityTypeConverterTest.cs @@ -5,7 +5,7 @@ using Microsoft.Maui; -namespace ReactiveUI.Tests.Maui; +namespace ReactiveUI.Maui.Tests; /// /// Tests for . @@ -21,9 +21,9 @@ public async Task GetAffinityForObjects_ReturnsCorrectAffinityForBoolToVisibilit { var converter = new BooleanToVisibilityTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(bool), typeof(Visibility)); + var affinity = converter.GetAffinityForObjects(); - await Assert.That(affinity).IsEqualTo(10); + await Assert.That(affinity).IsEqualTo(2); } /// @@ -35,23 +35,9 @@ public async Task GetAffinityForObjects_ReturnsCorrectAffinityForVisibilityToBoo { var converter = new BooleanToVisibilityTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(Visibility), typeof(bool)); + var affinity = converter.GetAffinityForObjects(); - await Assert.That(affinity).IsEqualTo(10); - } - - /// - /// Tests that GetAffinityForObjects returns zero for unsupported types. - /// - /// A representing the asynchronous operation. - [Test] - public async Task GetAffinityForObjects_ReturnsZeroForUnsupportedTypes() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var affinity = converter.GetAffinityForObjects(typeof(int), typeof(string)); - - await Assert.That(affinity).IsEqualTo(0); + await Assert.That(affinity).IsEqualTo(2); } /// @@ -63,7 +49,7 @@ public async Task TryConvert_ConvertsTrueToVisible() { var converter = new BooleanToVisibilityTypeConverter(); - var success = converter.TryConvert(true, typeof(Visibility), null, out var result); + var success = converter.TryConvertTyped(true, null, out var result); await Assert.That(success).IsTrue(); await Assert.That(result).IsEqualTo(Visibility.Visible); @@ -78,7 +64,7 @@ public async Task TryConvert_ConvertsFalseToCollapsed() { var converter = new BooleanToVisibilityTypeConverter(); - var success = converter.TryConvert(false, typeof(Visibility), null, out var result); + var success = converter.TryConvertTyped(false, null, out var result); await Assert.That(success).IsTrue(); await Assert.That(result).IsEqualTo(Visibility.Collapsed); @@ -93,7 +79,7 @@ public async Task TryConvert_WithInverseHint_InvertsConversion() { var converter = new BooleanToVisibilityTypeConverter(); - var success = converter.TryConvert(true, typeof(Visibility), BooleanToVisibilityHint.Inverse, out var result); + var success = converter.TryConvertTyped(true, BooleanToVisibilityHint.Inverse, out var result); await Assert.That(success).IsTrue(); await Assert.That(result).IsEqualTo(Visibility.Collapsed); @@ -109,7 +95,7 @@ public async Task TryConvert_WithUseHiddenHint_UsesHidden() { var converter = new BooleanToVisibilityTypeConverter(); - var success = converter.TryConvert(false, typeof(Visibility), BooleanToVisibilityHint.UseHidden, out var result); + var success = converter.TryConvertTyped(false, BooleanToVisibilityHint.UseHidden, out var result); await Assert.That(success).IsTrue(); await Assert.That(result).IsEqualTo(Visibility.Hidden); @@ -123,10 +109,10 @@ public async Task TryConvert_WithUseHiddenHint_UsesHidden() [Test] public async Task TryConvert_ConvertsVisibilityToBool() { - var converter = new BooleanToVisibilityTypeConverter(); + var converter = new VisibilityToBooleanTypeConverter(); - var successVisible = converter.TryConvert(Visibility.Visible, typeof(bool), null, out var resultVisible); - var successCollapsed = converter.TryConvert(Visibility.Collapsed, typeof(bool), null, out var resultCollapsed); + var successVisible = converter.TryConvertTyped(Visibility.Visible, null, out var resultVisible); + var successCollapsed = converter.TryConvertTyped(Visibility.Collapsed, null, out var resultCollapsed); await Assert.That(successVisible).IsTrue(); await Assert.That(successCollapsed).IsTrue(); @@ -139,18 +125,18 @@ public async Task TryConvert_ConvertsVisibilityToBool() } /// - /// Tests that TryConvert with non-Visibility input defaults to Visible. + /// Tests that TryConvert with non-bool input for boolean converter. /// /// A representing the asynchronous operation. [Test] - public async Task TryConvert_NonVisibilityInput_DefaultsToVisible() + public async Task TryConvert_BooleanConverter_HandlesInput() { var converter = new BooleanToVisibilityTypeConverter(); - var success = converter.TryConvert("some string", typeof(bool), null, out var result); + var success = converter.TryConvertTyped(false, null, out var result); await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(Visibility.Visible); + await Assert.That(result).IsEqualTo(Visibility.Collapsed); } /// @@ -160,14 +146,14 @@ public async Task TryConvert_NonVisibilityInput_DefaultsToVisible() [Test] public async Task TryConvert_VisibilityToBoolWithInverse_InvertsResult() { - var converter = new BooleanToVisibilityTypeConverter(); + var converter = new VisibilityToBooleanTypeConverter(); - var success = converter.TryConvert(Visibility.Visible, typeof(bool), BooleanToVisibilityHint.Inverse, out var result); + var success = converter.TryConvertTyped(Visibility.Visible, BooleanToVisibilityHint.Inverse, out var result); await Assert.That(success).IsTrue(); // With Inverse hint, Visible should become false - await Assert.That(result).IsEqualTo(true); + await Assert.That(result).IsEqualTo(false); } #if !HAS_UNO && !HAS_WINUI && !IS_MAUI @@ -180,9 +166,8 @@ public async Task TryConvert_WithInverseAndUseHidden_WorksCorrectly() { var converter = new BooleanToVisibilityTypeConverter(); - var success = converter.TryConvert( + var success = converter.TryConvertTyped( true, - typeof(Visibility), BooleanToVisibilityHint.Inverse | BooleanToVisibilityHint.UseHidden, out var result); diff --git a/src/tests/ReactiveUI.Maui.Tests/Builder/MauiDispatcherSchedulerTest.cs b/src/tests/ReactiveUI.Maui.Tests/Builder/MauiDispatcherSchedulerTest.cs index 76e0de59cb..f7cd74d902 100644 --- a/src/tests/ReactiveUI.Maui.Tests/Builder/MauiDispatcherSchedulerTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/Builder/MauiDispatcherSchedulerTest.cs @@ -6,32 +6,13 @@ using System.Reactive.Concurrency; using ReactiveUI.Builder; -namespace ReactiveUI.Tests.Maui.Builder; +namespace ReactiveUI.Maui.Tests.Builder; /// /// Tests for MauiDispatcherScheduler behavior. /// public class MauiDispatcherSchedulerTest { - private RxAppSchedulersScope? _schedulersScope; - - /// - /// Sets up the test by creating a scheduler scope. - /// - [Before(Test)] - public void SetUp() - { - _schedulersScope = new RxAppSchedulersScope(); - } - - /// - /// Tears down the test by disposing the scheduler scope. - /// - [After(Test)] - public void TearDown() - { - _schedulersScope?.Dispose(); - } /// /// Tests that dispatcher scheduler executes immediate work. @@ -40,13 +21,15 @@ public void TearDown() [Test] public async Task Dispatcher_ImmediateSchedule_ExecutesWork() { + RxAppBuilder.ResetForTesting(); var dispatcher = new TestDispatcher(); var builder = RxAppBuilder.CreateReactiveUIBuilder(); builder.WithMauiScheduler(dispatcher); + builder.WithCoreServices(); builder.BuildApp(); var executed = false; - RxApp.MainThreadScheduler.Schedule(() => executed = true); + RxSchedulers.MainThreadScheduler.Schedule(() => executed = true); await Assert.That(executed).IsTrue(); } @@ -58,13 +41,15 @@ public async Task Dispatcher_ImmediateSchedule_ExecutesWork() [Test] 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(); var executed = false; - RxApp.MainThreadScheduler.Schedule(() => executed = true); + RxSchedulers.MainThreadScheduler.Schedule(() => executed = true); await Assert.That(executed).IsTrue(); } @@ -76,13 +61,15 @@ public async Task Dispatcher_NoDispatchRequired_ExecutesImmediately() [Test] 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(); var executed = false; - RxApp.MainThreadScheduler.Schedule(() => executed = true); + RxSchedulers.MainThreadScheduler.Schedule(() => executed = true); await Assert.That(executed).IsTrue(); } @@ -138,38 +125,4 @@ public void Stop() } } } - - /// - /// A disposable scope that snapshots and restores RxApp scheduler state. - /// - private sealed class RxAppSchedulersScope : IDisposable - { - private readonly IScheduler _mainThreadScheduler; - private readonly IScheduler _taskpoolScheduler; - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// - public RxAppSchedulersScope() - { - _mainThreadScheduler = RxApp.MainThreadScheduler; - _taskpoolScheduler = RxApp.TaskpoolScheduler; - } - - /// - /// Restores the RxApp scheduler state. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - RxApp.MainThreadScheduler = _mainThreadScheduler; - RxApp.TaskpoolScheduler = _taskpoolScheduler; - _disposed = true; - } - } } diff --git a/src/tests/ReactiveUI.Maui.Tests/Builder/MauiReactiveUIBuilderExtensionsTest.cs b/src/tests/ReactiveUI.Maui.Tests/Builder/MauiReactiveUIBuilderExtensionsTest.cs index fc98b9a79e..d6988e483a 100644 --- a/src/tests/ReactiveUI.Maui.Tests/Builder/MauiReactiveUIBuilderExtensionsTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/Builder/MauiReactiveUIBuilderExtensionsTest.cs @@ -6,7 +6,7 @@ using Microsoft.Maui.Hosting; using ReactiveUI.Builder; -namespace ReactiveUI.Tests.Maui.Builder; +namespace ReactiveUI.Maui.Tests.Builder; /// /// Tests for . diff --git a/src/tests/ReactiveUI.Maui.Tests/MauiReactiveUIBuilderExtensionsTest.cs b/src/tests/ReactiveUI.Maui.Tests/MauiReactiveUIBuilderExtensionsTest.cs index 44e74a8d21..472fa6d272 100644 --- a/src/tests/ReactiveUI.Maui.Tests/MauiReactiveUIBuilderExtensionsTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/MauiReactiveUIBuilderExtensionsTest.cs @@ -3,10 +3,9 @@ // 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.Dispatching; using ReactiveUI.Builder; -namespace ReactiveUI.Tests.Maui; +namespace ReactiveUI.Maui.Tests; /// /// Tests for . diff --git a/src/tests/ReactiveUI.Maui.Tests/PlatformOperationsTest.cs b/src/tests/ReactiveUI.Maui.Tests/PlatformOperationsTest.cs index 1d89bc38e6..71cbed9390 100644 --- a/src/tests/ReactiveUI.Maui.Tests/PlatformOperationsTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/PlatformOperationsTest.cs @@ -3,7 +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. -namespace ReactiveUI.Tests.Maui; +namespace ReactiveUI.Maui.Tests; /// /// Tests for . diff --git a/src/tests/ReactiveUI.Maui.Tests/ReactivePageTest.cs b/src/tests/ReactiveUI.Maui.Tests/ReactivePageTest.cs index 99a5a2484b..00d9a6b60f 100644 --- a/src/tests/ReactiveUI.Maui.Tests/ReactivePageTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/ReactivePageTest.cs @@ -3,9 +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 ReactiveUI.Maui; - -namespace ReactiveUI.Tests.Maui; +namespace ReactiveUI.Maui.Tests; /// /// Tests for . diff --git a/src/tests/ReactiveUI.Maui.Tests/ReactiveShellContentTest.cs b/src/tests/ReactiveUI.Maui.Tests/ReactiveShellContentTest.cs index 731a5416af..6f86bb82e2 100644 --- a/src/tests/ReactiveUI.Maui.Tests/ReactiveShellContentTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/ReactiveShellContentTest.cs @@ -3,9 +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 ReactiveUI.Maui; - -namespace ReactiveUI.Tests.Maui; +namespace ReactiveUI.Maui.Tests; /// /// Tests for . diff --git a/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostTest.cs b/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostTest.cs index d82b256f89..c39ff3b988 100644 --- a/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostTest.cs @@ -3,10 +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.Reactive.Concurrency; -using Microsoft.Maui.Controls; - -namespace ReactiveUI.Tests.Maui; +namespace ReactiveUI.Maui.Tests; /// /// Tests for . diff --git a/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostTest.cs b/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostTest.cs index c61b1f6a41..9701c63170 100644 --- a/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostTest.cs @@ -3,9 +3,9 @@ // 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 System.Diagnostics.CodeAnalysis; -namespace ReactiveUI.Tests.Maui; +namespace ReactiveUI.Maui.Tests; /// /// Tests for . @@ -142,6 +142,11 @@ private class TestViewModel /// private class TestViewLocator : IViewLocator { - public IViewFor? ResolveView(T? viewModel, string? contract = null) => null; + public IViewFor? ResolveView(string? contract = null) + where TViewModel : class => null; + + [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 = null) => null; } } diff --git a/src/tests/ReactiveUI.NonParallel.Mobile.Tests/ReactiveUI.NonParallel.Mobile.Tests.csproj b/src/tests/ReactiveUI.NonParallel.Mobile.Tests/ReactiveUI.NonParallel.Mobile.Tests.csproj index 9b296c94aa..54c24f9783 100644 --- a/src/tests/ReactiveUI.NonParallel.Mobile.Tests/ReactiveUI.NonParallel.Mobile.Tests.csproj +++ b/src/tests/ReactiveUI.NonParallel.Mobile.Tests/ReactiveUI.NonParallel.Mobile.Tests.csproj @@ -14,8 +14,8 @@ + - @@ -42,6 +42,7 @@ + diff --git a/src/tests/ReactiveUI.NonParallel.Tests/AutoPersist/AutoPersistHelperTest.cs b/src/tests/ReactiveUI.NonParallel.Tests/AutoPersist/AutoPersistHelperTest.cs deleted file mode 100644 index 03a15f753d..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/AutoPersist/AutoPersistHelperTest.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2025 .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 Microsoft.Reactive.Testing; - -using ReactiveUI.Testing; - -namespace ReactiveUI.Tests.Core; - -[NotInParallel] -public class AutoPersistHelperTest -{ - /// - /// Test the automatic persist doesnt work on non data contract classes. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AutoPersistDoesntWorkOnNonDataContractClasses() - { - var fixture = new HostTestFixture(); - - var shouldDie = true; - try - { - fixture.AutoPersist(static _ => Observables.Unit); - } - catch (Exception) - { - shouldDie = false; - } - - await Assert.That(shouldDie).IsFalse(); - } - - /// - /// Test the automatic persist helper shouldnt trigger on non persistable properties. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AutoPersistHelperShouldntTriggerOnNonPersistableProperties() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); - var manualSave = new Subject(); - - var timesSaved = 0; - fixture.AutoPersist( - _ => - { - timesSaved++; - return Observables.Unit; - }, - manualSave, - TimeSpan.FromMilliseconds(100)); - - // No changes = no saving - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(0); - - // Change to not serialized = no saving - fixture.NotSerialized = "Foo"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(0); - }); - - /// - /// Tests the automatic persist helper saves on interval. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AutoPersistHelperSavesOnInterval() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); - var manualSave = new Subject(); - - var timesSaved = 0; - fixture.AutoPersist( - _ => - { - timesSaved++; - return Observables.Unit; - }, - manualSave, - TimeSpan.FromMilliseconds(100)); - - // No changes = no saving - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(0); - - // Change = one save - fixture.IsNotNullString = "Foo"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Two fast changes = one save - fixture.IsNotNullString = "Foo"; - fixture.IsNotNullString = "Bar"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(2); - - // Trigger save twice = one save - manualSave.OnNext(Unit.Default); - manualSave.OnNext(Unit.Default); - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(3); - }); - - /// - /// Tests the automatic persist helper disconnects. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AutoPersistHelperDisconnects() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); - var manualSave = new Subject(); - - var timesSaved = 0; - var disp = fixture.AutoPersist( - _ => - { - timesSaved++; - return Observables.Unit; - }, - manualSave, - TimeSpan.FromMilliseconds(100)); - - // No changes = no saving - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(0); - - // Change = one save - fixture.IsNotNullString = "Foo"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Two changes after dispose = no save - disp.Dispose(); - fixture.IsNotNullString = "Foo"; - fixture.IsNotNullString = "Bar"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Trigger save after dispose = no save - manualSave.OnNext(Unit.Default); - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - }); -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/AwaiterTest.cs b/src/tests/ReactiveUI.NonParallel.Tests/AwaiterTest.cs deleted file mode 100644 index 1a0cefaf37..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/AwaiterTest.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2025 .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.Infrastructure.StaticState; - -namespace ReactiveUI.Tests.Core; - -[NotInParallel] -public class AwaiterTest : IDisposable -{ - private RxAppSchedulersScope? _schedulersScope; - - [Before(Test)] - public void SetUp() - { - _schedulersScope = new RxAppSchedulersScope(); - } - - [After(Test)] - public void TearDown() - { - _schedulersScope?.Dispose(); - } - - /// - /// A smoke test for Awaiters. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AwaiterSmokeTest() - { - var fixture = AwaitAnObservable(); - fixture.Wait(); - - await Assert.That(fixture.Result).IsEqualTo(42); - } - - public void Dispose() - { - _schedulersScope?.Dispose(); - _schedulersScope = null; - } - - private static async Task AwaitAnObservable() - { - var o = Observable.Start( - static () => - { - Thread.Sleep(1000); - return 42; - }, - RxApp.TaskpoolScheduler); - - return await o; - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/CommandBinding/CommandBindingTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/CommandBinding/CommandBindingTests.cs deleted file mode 100644 index 8d2e702e69..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/CommandBinding/CommandBindingTests.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) 2025 .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; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; -using System.Windows.Input; -using ReactiveUI; -using ReactiveUI.Tests.Infrastructure.StaticState; -using Splat; - -namespace ReactiveUI.NonParallel.Tests -{ - /// - /// Tests for command binding. - /// - /// - /// This test fixture is marked as NotInParallel because tests call - /// Locator.CurrentMutable to register ICreatesCommandBinding implementations, - /// which mutate global service locator state. - /// - [NotInParallel] - public class CommandBindingTests - { - private LocatorScope? _locatorScope; - - [Before(Test)] - public void SetUp() - { - _locatorScope = new LocatorScope(); - - Locator.CurrentMutable.InitializeSplat(); - Locator.CurrentMutable.RegisterConstant(new CreatesCommandBindingViaEvent(), typeof(ICreatesCommandBinding)); - - // Register a custom binder to test binder resolution - Locator.CurrentMutable.RegisterConstant(new FakeCustomBinder(), typeof(ICreatesCommandBinding)); - } - - [After(Test)] - public void TearDown() - { - _locatorScope?.Dispose(); - } - - [Test] - public async Task CommandBinderImplementation_Should_Bind_Command_To_Event() - { - var binder = new CommandBinderImplementation(); - var viewModel = new FakeViewModel(); - var view = new FakeView { ViewModel = viewModel }; - - var disp = binder.BindCommand( - viewModel, - view, - vm => vm.Command, - v => v.Control, - Observable.Return((object?)null), - "Click"); - - await Assert.That(disp).IsNotNull(); - - bool executed = false; - viewModel.Command.Subscribe(_ => executed = true); - - view.Control.RaiseClick(); - - await Assert.That(executed).IsTrue(); - } - - [Test] - public async Task CommandBinderImplementation_Should_Use_Custom_Binder() - { - var binder = new CommandBinderImplementation(); - var viewModel = new FakeViewModel(); - var view = new FakeView { ViewModel = viewModel }; - - // FakeCustomControl has affinity with FakeCustomBinder - var disp = binder.BindCommand( - viewModel, - view, - vm => vm.Command, - v => v.CustomControl, - Observable.Return((object?)null)); - - await Assert.That(disp).IsNotNull(); - await Assert.That(FakeCustomBinder.BindCalled).IsTrue(); - } - - private class FakeViewModel : ReactiveObject - { - public ReactiveCommand Command { get; } = ReactiveCommand.Create(() => { }); - } - - private class FakeView : ReactiveObject, IViewFor - { - private FakeViewModel? _viewModel; - - public FakeViewModel? ViewModel - { - get => _viewModel; - set => this.RaiseAndSetIfChanged(ref _viewModel, value); - } - - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (FakeViewModel?)value; - } - - public FakeControl Control { get; } = new FakeControl(); - - public FakeCustomControl CustomControl { get; } = new FakeCustomControl(); - } - - private class FakeControl - { - public event EventHandler? Click; - - public void RaiseClick() - { - Click?.Invoke(this, EventArgs.Empty); - } - } - - private class FakeCustomControl - { - } - - private class FakeCustomBinder : ICreatesCommandBinding - { - public FakeCustomBinder() - { - BindCalled = false; - } - - public static bool BindCalled { get; set; } - - public int GetAffinityForObject(Type type, bool hasEventTarget) - { - if (type == typeof(FakeCustomControl)) - { - return 100; // High affinity - } - - return 0; - } - - public int GetAffinityForObject(bool hasEventTarget) - { - if (typeof(T) == typeof(FakeCustomControl)) - { - return 100; // High affinity - } - - return 0; - } - - public IDisposable? BindCommandToObject(ICommand? command, object? target, IObservable commandParameter) - { - BindCalled = true; - return Disposable.Empty; - } - - public IDisposable BindCommandToObject(ICommand? command, object? target, IObservable commandParameter, string eventName) - { - BindCalled = true; - return Disposable.Empty; - } - } - } -} \ No newline at end of file diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Commands/ReactiveCommandTest.cs b/src/tests/ReactiveUI.NonParallel.Tests/Commands/ReactiveCommandTest.cs deleted file mode 100644 index 5acae20714..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Commands/ReactiveCommandTest.cs +++ /dev/null @@ -1,1993 +0,0 @@ -// Copyright (c) 2025 .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.Input; - -using DynamicData; - -using Microsoft.Reactive.Testing; - -using ReactiveUI.Testing; -using ReactiveUI.Tests.Infrastructure.StaticState; - -namespace ReactiveUI.Tests.Core; - -[NotInParallel] -public class ReactiveCommandTest : IDisposable -{ - private RxAppSchedulersScope? _schedulersScope; - - public ReactiveCommandTest() - { - RxApp.EnsureInitialized(); - } - - [Before(Test)] - public void SetUp() - { - _schedulersScope = new RxAppSchedulersScope(); - } - - [After(Test)] - public void TearDown() - { - _schedulersScope?.Dispose(); - } - - /// - /// A test that determines whether this instance [can execute changed is available via ICommand]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanExecuteChangedIsAvailableViaICommand() - { - var canExecuteSubject = new Subject(); - ICommand fixture = - ReactiveCommand.Create( - () => Observables.Unit, - canExecuteSubject, - ImmediateScheduler.Instance); - var canExecuteChanged = new List(); - fixture.CanExecuteChanged += (_, __) => canExecuteChanged.Add(fixture.CanExecute(null)); - - canExecuteSubject.OnNext(true); - canExecuteSubject.OnNext(false); - - using (Assert.Multiple()) - { - await Assert.That(canExecuteChanged).Count().IsEqualTo(2); - await Assert.That(canExecuteChanged[0]).IsTrue(); - await Assert.That(canExecuteChanged[1]).IsFalse(); - } - } - - /// - /// A test that determines whether this instance [can execute is available via ICommand]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanExecuteIsAvailableViaICommand() - { - var canExecuteSubject = new Subject(); - ICommand fixture = - ReactiveCommand.Create( - static () => Observables.Unit, - canExecuteSubject, - ImmediateScheduler.Instance); - - using (Assert.Multiple()) - { - await Assert.That(fixture.CanExecute(null)).IsFalse(); - - canExecuteSubject.OnNext(true); - await Assert.That(fixture.CanExecute(null)).IsTrue(); - - canExecuteSubject.OnNext(false); - await Assert.That(fixture.CanExecute(null)).IsFalse(); - } - } - - /// - /// Test that determines whether this instance [can execute is behavioral]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanExecuteIsBehavioral() - { - var fixture = ReactiveCommand.Create( - static () => Observables.Unit, - outputScheduler: ImmediateScheduler.Instance); - fixture.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); - - using (Assert.Multiple()) - { - await Assert.That(canExecute).Count().IsEqualTo(1); - await Assert.That(canExecute[0]).IsTrue(); - } - } - - /// - /// Test that determines whether this instance [can execute is false if already executing]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanExecuteIsFalseIfAlreadyExecuting() => - await new TestScheduler().With(async scheduler => - { - var execute = Observables.Unit.Delay( - TimeSpan.FromSeconds(1), - scheduler); - var fixture = ReactiveCommand.CreateFromObservable( - () => execute, - outputScheduler: scheduler); - fixture.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); - - fixture.Execute().Subscribe(); - scheduler.AdvanceByMs(100); - - using (Assert.Multiple()) - { - await Assert.That(canExecute).Count().IsEqualTo(2); - await Assert.That(canExecute[1]).IsFalse(); - } - - scheduler.AdvanceByMs(901); - - using (Assert.Multiple()) - { - await Assert.That(canExecute).Count().IsEqualTo(3); - await Assert.That(canExecute[2]).IsTrue(); - } - }); - - /// - /// Test that determines whether this instance [can execute is false if caller dictates as such]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanExecuteIsFalseIfCallerDictatesAsSuch() - { - var canExecuteSubject = new Subject(); - var fixture = ReactiveCommand.Create( - static () => Observables.Unit, - canExecuteSubject, - ImmediateScheduler.Instance); - fixture.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); - - canExecuteSubject.OnNext(true); - canExecuteSubject.OnNext(false); - - using (Assert.Multiple()) - { - await Assert.That(canExecute).Count().IsEqualTo(3); - await Assert.That(canExecute[0]).IsFalse(); - await Assert.That(canExecute[1]).IsTrue(); - await Assert.That(canExecute[2]).IsFalse(); - } - } - - /// - /// Test that determines whether this instance [can execute is unsubscribed after command disposal]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanExecuteIsUnsubscribedAfterCommandDisposal() - { - var canExecuteSubject = new Subject(); - var fixture = ReactiveCommand.Create( - static () => Observables.Unit, - canExecuteSubject, - ImmediateScheduler.Instance); - - await Assert.That(canExecuteSubject.HasObservers).IsTrue(); - - fixture.Dispose(); - - await Assert.That(canExecuteSubject.HasObservers).IsFalse(); - } - - /// - /// Test that determines whether this instance [can execute only ticks distinct values]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanExecuteOnlyTicksDistinctValues() - { - var canExecuteSubject = new Subject(); - var fixture = ReactiveCommand.Create( - static () => Observables.Unit, - canExecuteSubject, - ImmediateScheduler.Instance); - fixture.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); - - canExecuteSubject.OnNext(false); - canExecuteSubject.OnNext(false); - canExecuteSubject.OnNext(false); - canExecuteSubject.OnNext(false); - canExecuteSubject.OnNext(true); - canExecuteSubject.OnNext(true); - - using (Assert.Multiple()) - { - await Assert.That(canExecute).Count().IsEqualTo(2); - await Assert.That(canExecute[0]).IsFalse(); - await Assert.That(canExecute[1]).IsTrue(); - } - } - - /// - /// Test that determines whether this instance [can execute ticks failures through thrown exceptions]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanExecuteTicksFailuresThroughThrownExceptions() - { - var canExecuteSubject = new Subject(); - var fixture = ReactiveCommand.Create( - static () => Observables.Unit, - canExecuteSubject, - ImmediateScheduler.Instance); - fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions) - .Subscribe(); - - canExecuteSubject.OnError(new InvalidOperationException("oops")); - - using (Assert.Multiple()) - { - await Assert.That(thrownExceptions).Count().IsEqualTo(1); - await Assert.That(thrownExceptions[0].Message).IsEqualTo("oops"); - } - } - - /// - /// Creates the task facilitates TPL integration. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CreateTaskFacilitatesTPLIntegration() - { - var fixture = - ReactiveCommand.CreateFromTask( - static () => Task.FromResult(13), - outputScheduler: ImmediateScheduler.Instance); - fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); - - fixture.Execute().Subscribe(); - - using (Assert.Multiple()) - { - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo(13); - } - } - - /// - /// Creates the task facilitates TPL integration with parameter. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CreateTaskFacilitatesTPLIntegrationWithParameter() - { - var fixture = - ReactiveCommand.CreateFromTask( - static param => Task.FromResult(param + 1), - outputScheduler: ImmediateScheduler.Instance); - fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); - - fixture.Execute(3).Subscribe(); - fixture.Execute(41).Subscribe(); - - using (Assert.Multiple()) - { - await Assert.That(results).Count().IsEqualTo(2); - await Assert.That(results[0]).IsEqualTo(4); - await Assert.That(results[1]).IsEqualTo(42); - } - } - - /// - /// Creates the throws if execution parameter is null. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CreateThrowsIfExecutionParameterIsNull() - { - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.Create(null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.Create((Func)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.Create((Action)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.Create((Func)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.Create((Func>)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.Create((Func>)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.Create((Func>)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.Create((Func>)null!); - await Task.CompletedTask; - }); - } - - /// - /// Creates the throws if execution parameter is null (RunInBackground). - /// - /// A representing the asynchronous operation. - [Test] - public async Task CreateRunInBackgroundThrowsIfExecutionParameterIsNull() - { - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.CreateRunInBackground(null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.CreateRunInBackground((Func)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.CreateRunInBackground((Action)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.CreateRunInBackground((Func)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.CreateRunInBackground((Func>)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.CreateRunInBackground((Func>)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.CreateRunInBackground((Func>)null!); - await Task.CompletedTask; - }); - await Assert.ThrowsExactlyAsync(async () => - { - ReactiveCommand.CreateRunInBackground((Func>)null!); - await Task.CompletedTask; - }); - } - - /// - /// Exceptions are delivered on output scheduler. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExceptionsAreDeliveredOnOutputScheduler() => - await new TestScheduler().With(async scheduler => - { - var fixture = - ReactiveCommand.CreateFromObservable( - () => Observable.Throw(new InvalidOperationException()), - outputScheduler: scheduler); - Exception? exception = null; - fixture.ThrownExceptions.Subscribe(ex => exception = ex); - fixture.Execute().Subscribe( - _ => { }, - _ => { }); - - await Assert.That(exception).IsNull(); - scheduler.Start(); - await Assert.That(exception).IsTypeOf(); - }); - - /// - /// Executes can be cancelled. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteCanBeCancelled() => - await new TestScheduler().With(async scheduler => - { - var execute = Observables.Unit.Delay( - TimeSpan.FromSeconds(1), - scheduler); - var fixture = ReactiveCommand.CreateFromObservable( - () => execute, - outputScheduler: scheduler); - fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var executed).Subscribe(); - - var sub1 = fixture.Execute().Subscribe(); - var sub2 = fixture.Execute().Subscribe(); - scheduler.AdvanceByMs(999); - - using (Assert.Multiple()) - { - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsTrue(); - await Assert.That(executed).IsEmpty(); - } - - sub1.Dispose(); - - scheduler.AdvanceByMs(2); - - using (Assert.Multiple()) - { - await Assert.That(executed).Count().IsEqualTo(1); - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsFalse(); - } - }); - - /// - /// Executes can tick through multiple results. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteCanTickThroughMultipleResults() - { - var fixture = ReactiveCommand.CreateFromObservable( - static () => new[] { 1, 2, 3 }.ToObservable(), - outputScheduler: ImmediateScheduler.Instance); - fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); - - fixture.Execute().Subscribe(); - - using (Assert.Multiple()) - { - await Assert.That(results).Count().IsEqualTo(3); - await Assert.That(results[0]).IsEqualTo(1); - await Assert.That(results[1]).IsEqualTo(2); - await Assert.That(results[2]).IsEqualTo(3); - } - } - - /// - /// Executes facilitates any number of in flight executions. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteFacilitatesAnyNumberOfInFlightExecutions() => - await new TestScheduler().With(async scheduler => - { - var execute = Observables.Unit.Delay( - TimeSpan.FromMilliseconds(500), - scheduler); - var fixture = ReactiveCommand.CreateFromObservable( - () => execute, - outputScheduler: scheduler); - fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var executed).Subscribe(); - - var sub1 = fixture.Execute().Subscribe(); - var sub2 = fixture.Execute().Subscribe(); - scheduler.AdvanceByMs(100); - - var sub3 = fixture.Execute().Subscribe(); - scheduler.AdvanceByMs(200); - var sub4 = fixture.Execute().Subscribe(); - scheduler.AdvanceByMs(100); - - using (Assert.Multiple()) - { - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsTrue(); - await Assert.That(executed).IsEmpty(); - } - - scheduler.AdvanceByMs(101); - - using (Assert.Multiple()) - { - await Assert.That(executed).Count().IsEqualTo(2); - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsTrue(); - } - - scheduler.AdvanceByMs(200); - - using (Assert.Multiple()) - { - await Assert.That(executed).Count().IsEqualTo(3); - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsTrue(); - } - - scheduler.AdvanceByMs(100); - - using (Assert.Multiple()) - { - await Assert.That(executed).Count().IsEqualTo(4); - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsFalse(); - } - }); - - /// - /// Execute is available via ICommand. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteIsAvailableViaICommand() - { - var executed = false; - ICommand fixture = ReactiveCommand.Create( - () => - { - executed = true; - return Observables.Unit; - }, - outputScheduler: ImmediateScheduler.Instance); - - fixture.Execute(null); - await Assert.That(executed).IsTrue(); - } - - /// - /// Execute passes through parameter. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecutePassesThroughParameter() - { - var parameters = new List(); - var fixture = ReactiveCommand.CreateFromObservable( - param => - { - parameters.Add(param); - return Observables.Unit; - }, - outputScheduler: ImmediateScheduler.Instance); - - fixture.Execute(1).Subscribe(); - fixture.Execute(42).Subscribe(); - fixture.Execute(348).Subscribe(); - - using (Assert.Multiple()) - { - await Assert.That(parameters).Count().IsEqualTo(3); - await Assert.That(parameters[0]).IsEqualTo(1); - await Assert.That(parameters[1]).IsEqualTo(42); - await Assert.That(parameters[2]).IsEqualTo(348); - } - } - - /// - /// Execute re-enables execution even after failure. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteReenablesExecutionEvenAfterFailure() - { - var fixture = - ReactiveCommand.CreateFromObservable( - static () => Observable.Throw(new InvalidOperationException("oops")), - outputScheduler: ImmediateScheduler.Instance); - fixture.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); - fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions) - .Subscribe(); - - fixture.Execute().Subscribe( - static _ => { }, - static _ => { }); - - using (Assert.Multiple()) - { - await Assert.That(thrownExceptions).Count().IsEqualTo(1); - await Assert.That(thrownExceptions[0].Message).IsEqualTo("oops"); - await Assert.That(canExecute).Count().IsEqualTo(3); - await Assert.That(canExecute[0]).IsTrue(); - await Assert.That(canExecute[1]).IsFalse(); - await Assert.That(canExecute[2]).IsTrue(); - } - } - - /// - /// Execute result is delivered on specified scheduler. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteResultIsDeliveredOnSpecifiedScheduler() => - await new TestScheduler().With(async scheduler => - { - var execute = Observables.Unit; - var fixture = ReactiveCommand.CreateFromObservable( - () => execute, - outputScheduler: scheduler); - var executed = false; - - fixture.Execute().ObserveOn(scheduler).Subscribe(_ => executed = true); - - await Assert.That(executed).IsFalse(); - scheduler.AdvanceByMs(1); - await Assert.That(executed).IsTrue(); - }); - - /// - /// Execute ticks any exception. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteTicksAnyException() - { - var fixture = - ReactiveCommand.CreateFromObservable( - () => Observable.Throw(new InvalidOperationException()), - outputScheduler: ImmediateScheduler.Instance); - fixture.ThrownExceptions.Subscribe(); - Exception? exception = null; - fixture.Execute().Subscribe( - _ => { }, - ex => exception = ex, - () => { }); - - await Assert.That(exception).IsTypeOf(); - } - - /// - /// Execute ticks any lambda exception. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteTicksAnyLambdaException() - { - var fixture = ReactiveCommand.CreateFromObservable( - () => throw new InvalidOperationException(), - outputScheduler: ImmediateScheduler.Instance); - fixture.ThrownExceptions.Subscribe(); - Exception? exception = null; - fixture.Execute().Subscribe( - _ => { }, - ex => exception = ex, - () => { }); - - await Assert.That(exception).IsTypeOf(); - } - - /// - /// Execute ticks errors through thrown exceptions. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteTicksErrorsThroughThrownExceptions() - { - var fixture = - ReactiveCommand.CreateFromObservable( - static () => Observable.Throw(new InvalidOperationException("oops")), - outputScheduler: ImmediateScheduler.Instance); - fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions) - .Subscribe(); - - fixture.Execute().Subscribe( - static _ => { }, - static _ => { }); - - using (Assert.Multiple()) - { - await Assert.That(thrownExceptions).Count().IsEqualTo(1); - await Assert.That(thrownExceptions[0].Message).IsEqualTo("oops"); - } - } - - /// - /// Execute ticks lambda errors through thrown exceptions. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteTicksLambdaErrorsThroughThrownExceptions() - { - var fixture = ReactiveCommand.CreateFromObservable( - static () => throw new InvalidOperationException("oops"), - outputScheduler: ImmediateScheduler.Instance); - fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions) - .Subscribe(); - - fixture.Execute().Subscribe( - static _ => { }, - static _ => { }); - - using (Assert.Multiple()) - { - await Assert.That(thrownExceptions).Count().IsEqualTo(1); - await Assert.That(thrownExceptions[0].Message).IsEqualTo("oops"); - await Assert.That(fixture.CanExecute.FirstAsync().Wait()).IsTrue(); - } - } - - /// - /// Execute ticks through the result. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteTicksThroughTheResult() - { - var num = 0; - var fixture = - ReactiveCommand.CreateFromObservable( - () => Observable.Return(num), - outputScheduler: ImmediateScheduler.Instance); - fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); - - num = 1; - fixture.Execute().Subscribe(); - num = 10; - fixture.Execute().Subscribe(); - num = 30; - fixture.Execute().Subscribe(); - - using (Assert.Multiple()) - { - await Assert.That(results).Count().IsEqualTo(3); - await Assert.That(results[0]).IsEqualTo(1); - await Assert.That(results[1]).IsEqualTo(10); - await Assert.That(results[2]).IsEqualTo(30); - } - } - - /// - /// Execute via ICommand throws if parameter type is incorrect. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteViaICommandThrowsIfParameterTypeIsIncorrect() - { - ICommand fixture = ReactiveCommand.Create( - _ => { }, - outputScheduler: ImmediateScheduler.Instance); - var ex = Assert.Throws(() => fixture.Execute("foo")); - await Assert.That(ex!.Message).IsEqualTo("Command requires parameters of type System.Int32, but received parameter of type System.String."); - - fixture = ReactiveCommand.Create(_ => { }); - ex = Assert.Throws(() => fixture.Execute(13)); - await Assert.That(ex!.Message).IsEqualTo("Command requires parameters of type System.String, but received parameter of type System.Int32."); - } - - /// - /// Execute via ICommand works with nullable types. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExecuteViaICommandWorksWithNullableTypes() - { - int? value = null; - ICommand fixture = - ReactiveCommand.Create( - param => value = param, - outputScheduler: ImmediateScheduler.Instance); - - fixture.Execute(42); - await Assert.That(value).IsEqualTo(42); - - fixture.Execute(null); - await Assert.That(value).IsNull(); - } - - /// - /// Invoke command against ICommand in target invokes the command. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandInTargetInvokesTheCommand() - { - var executionCount = 0; - var fixture = new ICommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand!); - fixture.TheCommand = - ReactiveCommand.Create( - () => ++executionCount, - outputScheduler: ImmediateScheduler.Instance); - - source.OnNext(Unit.Default); - await Assert.That(executionCount).IsEqualTo(1); - - source.OnNext(Unit.Default); - await Assert.That(executionCount).IsEqualTo(2); - } - - /// - /// Invoke command against ICommand in target passes the specified value. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandInTargetPassesTheSpecifiedValueToCanExecuteAndExecute() - { - var fixture = new ICommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - static x => x!.TheCommand!); - var command = new FakeCommand(); - fixture.TheCommand = command; - - source.OnNext(42); - using (Assert.Multiple()) - { - await Assert.That(command.CanExecuteParameter).IsEqualTo(42); - await Assert.That(command.ExecuteParameter).IsEqualTo(42); - } - } - - /// - /// Invoke command against nullable ICommand in target passes the specified value. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandInNullableTargetPassesTheSpecifiedValueToCanExecuteAndExecute() - { - var fixture = new ICommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - static x => x.TheCommand); - var command = new FakeCommand(); - fixture.TheCommand = command; - - source.OnNext(42); - using (Assert.Multiple()) - { - await Assert.That(command.CanExecuteParameter).IsEqualTo(42); - await Assert.That(command.ExecuteParameter).IsEqualTo(42); - } - } - - /// - /// Invoke command against ICommand in target respects can execute. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandInTargetRespectsCanExecute() - { - var executed = false; - var canExecute = new BehaviorSubject(false); - var fixture = new ICommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand!); - fixture.TheCommand = ReactiveCommand.Create( - () => executed = true, - canExecute, - ImmediateScheduler.Instance); - - source.OnNext(Unit.Default); - await Assert.That(executed).IsFalse(); - - canExecute.OnNext(true); - source.OnNext(Unit.Default); - await Assert.That(executed).IsTrue(); - } - - /// - /// Invoke command against nullable target respects can execute window. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandInNullableTargetRespectsCanExecute() - { - var executed = false; - var canExecute = new BehaviorSubject(false); - var fixture = new ICommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand); - fixture.TheCommand = ReactiveCommand.Create( - () => executed = true, - canExecute, - ImmediateScheduler.Instance); - - source.OnNext(Unit.Default); - await Assert.That(executed).IsFalse(); - - canExecute.OnNext(true); - source.OnNext(Unit.Default); - await Assert.That(executed).IsTrue(); - } - - /// - /// Invoke command against ICommand in target respects can execute window. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandInTargetRespectsCanExecuteWindow() - { - var executed = false; - var canExecute = new BehaviorSubject(false); - var fixture = new ICommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand!); - fixture.TheCommand = ReactiveCommand.Create( - () => executed = true, - canExecute, - ImmediateScheduler.Instance); - - source.OnNext(Unit.Default); - await Assert.That(executed).IsFalse(); - - // When the window reopens, previous requests should NOT execute. - canExecute.OnNext(true); - await Assert.That(executed).IsFalse(); - } - - /// - /// Invoke command against ICommand in target swallows exceptions. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandInTargetSwallowsExceptions() - { - var count = 0; - var fixture = new ICommandHolder(); - var command = ReactiveCommand.Create( - () => - { - ++count; - throw new InvalidOperationException(); - }, - outputScheduler: ImmediateScheduler.Instance); - command.ThrownExceptions.Subscribe(); - fixture.TheCommand = command; - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand!); - - source.OnNext(Unit.Default); - source.OnNext(Unit.Default); - - await Assert.That(count).IsEqualTo(2); - } - - /// - /// Invoke command against ICommand invokes the command. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandInvokesTheCommand() - { - var executionCount = 0; - ICommand fixture = ReactiveCommand.Create( - () => ++executionCount, - outputScheduler: ImmediateScheduler.Instance); - var source = new Subject(); - source.InvokeCommand(fixture); - - source.OnNext(Unit.Default); - await Assert.That(executionCount).IsEqualTo(1); - - source.OnNext(Unit.Default); - await Assert.That(executionCount).IsEqualTo(2); - } - - /// - /// Invoke command against nullable ICommand invokes the command. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstNullableICommandInvokesTheCommand() - { - var executionCount = 0; - ICommand fixture = - ReactiveCommand.Create( - () => ++executionCount, - outputScheduler: ImmediateScheduler.Instance); - var source = new Subject(); - source.InvokeCommand(fixture); - - source.OnNext(Unit.Default); - await Assert.That(executionCount).IsEqualTo(1); - - source.OnNext(Unit.Default); - await Assert.That(executionCount).IsEqualTo(2); - } - - /// - /// Invoke command against ICommand passes the specified value. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandPassesTheSpecifiedValueToCanExecuteAndExecute() - { - var fixture = new FakeCommand(); - var source = new Subject(); - source.InvokeCommand(fixture); - - source.OnNext(42); - using (Assert.Multiple()) - { - await Assert.That(fixture.CanExecuteParameter).IsEqualTo(42); - await Assert.That(fixture.ExecuteParameter).IsEqualTo(42); - } - } - - /// - /// Invoke command against ICommand respects can execute. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstICommandRespectsCanExecute() - { - var executed = false; - var canExecute = new BehaviorSubject(false); - ICommand fixture = ReactiveCommand.Create( - () => executed = true, - canExecute, - ImmediateScheduler.Instance); - var source = new Subject(); - source.InvokeCommand(fixture); - - source.OnNext(Unit.Default); - await Assert.That(executed).IsFalse(); - - canExecute.OnNext(true); - source.OnNext(Unit.Default); - await Assert.That(executed).IsTrue(); - } - - /// - /// Invoke command against reactive command in target invokes the command. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandInTargetInvokesTheCommand() - { - var executionCount = 0; - var fixture = new ReactiveCommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand!); - fixture.TheCommand = - ReactiveCommand.Create( - _ => ++executionCount, - outputScheduler: ImmediateScheduler.Instance); - - source.OnNext(0); - await Assert.That(executionCount).IsEqualTo(1); - - source.OnNext(0); - await Assert.That(executionCount).IsEqualTo(2); - } - - /// - /// Invoke command against reactive command in target passes the specified value to execute. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandInTargetPassesTheSpecifiedValueToExecute() - { - var executeReceived = 0; - var fixture = new ReactiveCommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand!); - fixture.TheCommand = - ReactiveCommand.Create( - x => executeReceived = x, - outputScheduler: ImmediateScheduler.Instance); - - source.OnNext(42); - await Assert.That(executeReceived).IsEqualTo(42); - } - - /// - /// Invoke command against reactive command in target respects can execute. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandInTargetRespectsCanExecute() - { - var executed = false; - var canExecute = new BehaviorSubject(false); - var fixture = new ReactiveCommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand!); - fixture.TheCommand = ReactiveCommand.Create( - _ => executed = true, - canExecute, - ImmediateScheduler.Instance); - - source.OnNext(0); - await Assert.That(executed).IsFalse(); - - canExecute.OnNext(true); - source.OnNext(0); - await Assert.That(executed).IsTrue(); - } - - /// - /// Invoke command against reactive command in target respects can execute window. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandInTargetRespectsCanExecuteWindow() - { - var executed = false; - var canExecute = new BehaviorSubject(false); - var fixture = new ReactiveCommandHolder(); - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand!); - fixture.TheCommand = ReactiveCommand.Create( - _ => executed = true, - canExecute, - ImmediateScheduler.Instance); - - source.OnNext(0); - await Assert.That(executed).IsFalse(); - - // When the window reopens, previous requests should NOT execute. - canExecute.OnNext(true); - await Assert.That(executed).IsFalse(); - } - - /// - /// Invoke command against reactive command in target swallows exceptions. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandInTargetSwallowsExceptions() - { - var count = 0; - var fixture = new ReactiveCommandHolder - { - TheCommand = ReactiveCommand.Create( - _ => - { - ++count; - throw new InvalidOperationException(); - }, - outputScheduler: ImmediateScheduler.Instance) - }; - fixture.TheCommand!.ThrownExceptions.Subscribe(); - var source = new Subject(); - source.InvokeCommand( - fixture, - x => x.TheCommand!); - - source.OnNext(0); - source.OnNext(0); - - await Assert.That(count).IsEqualTo(2); - } - - /// - /// Invoke command against reactive command invokes the command. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandInvokesTheCommand() - { - var executionCount = 0; - var fixture = ReactiveCommand.Create( - () => ++executionCount, - outputScheduler: ImmediateScheduler.Instance); - var source = new Subject(); - source.InvokeCommand(fixture); - - source.OnNext(Unit.Default); - await Assert.That(executionCount).IsEqualTo(1); - - source.OnNext(Unit.Default); - await Assert.That(executionCount).IsEqualTo(2); - } - - /// - /// Invoke command against reactive command passes the specified value. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandPassesTheSpecifiedValueToExecute() - { - var executeReceived = 0; - var fixture = - ReactiveCommand.Create( - x => executeReceived = x, - outputScheduler: ImmediateScheduler.Instance); - var source = new Subject(); - source.InvokeCommand(fixture); - - source.OnNext(42); - await Assert.That(executeReceived).IsEqualTo(42); - } - - /// - /// Invoke command against reactive command respects can execute. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandRespectsCanExecute() - { - var executed = false; - var canExecute = new BehaviorSubject(false); - var fixture = ReactiveCommand.Create( - () => executed = true, - canExecute, - ImmediateScheduler.Instance); - var source = new Subject(); - source.InvokeCommand(fixture); - - source.OnNext(Unit.Default); - await Assert.That(executed).IsFalse(); - - canExecute.OnNext(true); - source.OnNext(Unit.Default); - await Assert.That(executed).IsTrue(); - } - - /// - /// Invoke command against reactive command respects can execute window. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandRespectsCanExecuteWindow() - { - var executed = false; - var canExecute = new BehaviorSubject(false); - var fixture = ReactiveCommand.Create( - () => executed = true, - canExecute, - outputScheduler: ImmediateScheduler.Instance); - var source = new Subject(); - source.InvokeCommand(fixture); - - source.OnNext(Unit.Default); - await Assert.That(executed).IsFalse(); - - // When the window reopens, previous requests should NOT execute. - canExecute.OnNext(true); - await Assert.That(executed).IsFalse(); - } - - /// - /// Invoke command against reactive command swallows exceptions. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandAgainstReactiveCommandSwallowsExceptions() - { - var count = 0; - var fixture = ReactiveCommand.Create( - () => - { - ++count; - throw new InvalidOperationException(); - }, - outputScheduler: ImmediateScheduler.Instance); - fixture.ThrownExceptions.Subscribe(); - var source = new Subject(); - source.InvokeCommand(fixture); - - source.OnNext(Unit.Default); - source.OnNext(Unit.Default); - - await Assert.That(count).IsEqualTo(2); - } - - /// - /// Invoke command works even if the source is cold. - /// - /// A representing the asynchronous operation. - [Test] - public async Task InvokeCommandWorksEvenIfTheSourceIsCold() - { - var executionCount = 0; - var fixture = ReactiveCommand.Create( - () => ++executionCount, - outputScheduler: ImmediateScheduler.Instance); - var source = Observable.Return(Unit.Default); - source.InvokeCommand(fixture); - - await Assert.That(executionCount).IsEqualTo(1); - } - - /// - /// IsExecuting is behavioral. - /// - /// A representing the asynchronous operation. - [Test] - public async Task IsExecutingIsBehavioral() - { - var fixture = ReactiveCommand.Create( - static () => Observables.Unit, - outputScheduler: ImmediateScheduler.Instance); - fixture.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var isExecuting).Subscribe(); - - using (Assert.Multiple()) - { - await Assert.That(isExecuting).Count().IsEqualTo(1); - await Assert.That(isExecuting[0]).IsFalse(); - } - } - - /// - /// IsExecuting remains true as long as pipeline has not completed. - /// - /// A representing the asynchronous operation. - [Test] - public async Task IsExecutingRemainsTrueAsLongAsExecutionPipelineHasNotCompleted() - { - var execute = new Subject(); - var fixture = ReactiveCommand.CreateFromObservable( - () => execute, - outputScheduler: ImmediateScheduler.Instance); - - fixture.Execute().Subscribe(); - - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsTrue(); - - execute.OnNext(Unit.Default); - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsTrue(); - - execute.OnNext(Unit.Default); - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsTrue(); - - execute.OnCompleted(); - await Assert.That(fixture.IsExecuting.FirstAsync().Wait()).IsFalse(); - } - - /// - /// IsExecuting ticks as executions progress. - /// - /// A representing the asynchronous operation. - [Test] - public async Task IsExecutingTicksAsExecutionsProgress() => - await new TestScheduler().With(async scheduler => - { - var execute = Observables.Unit.Delay( - TimeSpan.FromSeconds(1), - scheduler); - var fixture = ReactiveCommand.CreateFromObservable( - () => execute, - outputScheduler: scheduler); - fixture.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var isExecuting) - .Subscribe(); - - fixture.Execute().Subscribe(); - scheduler.AdvanceByMs(100); - - using (Assert.Multiple()) - { - await Assert.That(isExecuting).Count().IsEqualTo(2); - await Assert.That(isExecuting[0]).IsFalse(); - await Assert.That(isExecuting[1]).IsTrue(); - } - - scheduler.AdvanceByMs(901); - - using (Assert.Multiple()) - { - await Assert.That(isExecuting).Count().IsEqualTo(3); - await Assert.That(isExecuting[2]).IsFalse(); - } - }); - - /// - /// Result is ticked through specified scheduler. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ResultIsTickedThroughSpecifiedScheduler() => - await new TestScheduler().WithAsync(static async scheduler => - { - var fixture = ReactiveCommand.CreateRunInBackground( - static () => Observables.Unit, - outputScheduler: scheduler); - fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); - - fixture.Execute().Subscribe(); - await Assert.That(results).IsEmpty(); - - scheduler.AdvanceByMs(1); - await Assert.That(results).Count().IsEqualTo(1); - return Task.CompletedTask; - }); - - /// - /// Synchronous command executes lazily. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SynchronousCommandExecuteLazily() - { - var executionCount = 0; - var fixture1 = - ReactiveCommand.Create( - () => ++executionCount, - outputScheduler: ImmediateScheduler.Instance); - var fixture2 = - ReactiveCommand.Create( - _ => ++executionCount, - outputScheduler: ImmediateScheduler.Instance); - var fixture3 = ReactiveCommand.Create( - () => - { - ++executionCount; - return 42; - }, - outputScheduler: ImmediateScheduler.Instance); - var fixture4 = ReactiveCommand.Create( - _ => - { - ++executionCount; - return 42; - }, - outputScheduler: ImmediateScheduler.Instance); - - var execute1 = fixture1.Execute(); - var execute2 = fixture2.Execute(); - var execute3 = fixture3.Execute(); - var execute4 = fixture4.Execute(); - - await Assert.That(executionCount).IsEqualTo(0); - - execute1.Subscribe(); - await Assert.That(executionCount).IsEqualTo(1); - - execute2.Subscribe(); - await Assert.That(executionCount).IsEqualTo(2); - - execute3.Subscribe(); - await Assert.That(executionCount).IsEqualTo(3); - - execute4.Subscribe(); - await Assert.That(executionCount).IsEqualTo(4); - } - - /// - /// Synchronous commands fail correctly. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SynchronousCommandsFailCorrectly() - { - var fixture1 = ReactiveCommand.Create( - () => throw new InvalidOperationException(), - outputScheduler: ImmediateScheduler.Instance); - var fixture2 = ReactiveCommand.Create( - _ => throw new InvalidOperationException(), - outputScheduler: ImmediateScheduler.Instance); - var fixture3 = ReactiveCommand.Create( - () => throw new InvalidOperationException(), - outputScheduler: ImmediateScheduler.Instance); - var fixture4 = ReactiveCommand.Create( - _ => throw new InvalidOperationException(), - outputScheduler: ImmediateScheduler.Instance); - - var failureCount = 0; - Observable.Merge( - fixture1.ThrownExceptions, - fixture2.ThrownExceptions, - fixture3.ThrownExceptions, - fixture4.ThrownExceptions).Subscribe(_ => ++failureCount); - - fixture1.Execute().Subscribe( - _ => { }, - _ => { }); - await Assert.That(failureCount).IsEqualTo(1); - - fixture2.Execute().Subscribe( - _ => { }, - _ => { }); - await Assert.That(failureCount).IsEqualTo(2); - - fixture3.Execute().Subscribe( - _ => { }, - _ => { }); - await Assert.That(failureCount).IsEqualTo(3); - - fixture4.Execute().Subscribe( - _ => { }, - _ => { }); - await Assert.That(failureCount).IsEqualTo(4); - } - - [Test] - public async Task ReactiveCommandCreateFromTaskHandlesTaskExceptionAsync() - { - var tcsStart = new TaskCompletionSource(); - var isExecutingList = new List(); - Exception? fail = null; - - var fixture = ReactiveCommand.CreateFromTask( - async _ => - { - await tcsStart.Task; - throw new Exception("break execution"); - }, - outputScheduler: ImmediateScheduler.Instance); - - fixture.IsExecuting.Subscribe(x => isExecutingList.Add(x)); - fixture.ThrownExceptions.Subscribe(ex => fail = ex); - - await Assert.That(isExecutingList.LastOrDefault()).IsFalse(); - await Assert.That(fail).IsNull(); - - fixture.Execute().Subscribe(); - - // Wait for execution to start (IsExecuting should become true) - await Task.Delay(100); - await Assert.That(isExecutingList.LastOrDefault()).IsTrue(); - await Assert.That(fail).IsNull(); - - // Signal task to proceed and throw - tcsStart.SetResult(Unit.Default); - - // Wait for completion (IsExecuting should become false) - await Task.Delay(100); - await Assert.That(isExecutingList.LastOrDefault()).IsFalse(); - - using (Assert.Multiple()) - { - await Assert.That(fail?.Message).IsEqualTo("break execution"); - } - } - - [Test] - public async Task ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync() - { - var tcsStarted = new TaskCompletionSource(); - var tcsCaught = new TaskCompletionSource(); - var tcsFinish = new TaskCompletionSource(); - - var statusTrail = new List<(int Position, string Status)>(); - var position = 0; - - var fixture = ReactiveCommand.CreateFromTask( - async (token) => - { - statusTrail.Add((Interlocked.Increment(ref position) - 1, "started command")); - tcsStarted.TrySetResult(Unit.Default); - try - { - await Task.Delay( - 10000, - token); - } - catch (OperationCanceledException) - { - tcsCaught.TrySetResult(Unit.Default); - await tcsFinish.Task; - throw; - } - }, - outputScheduler: ImmediateScheduler.Instance); - - // Subscribe to ThrownExceptions so RxApp.DefaultExceptionHandler doesn't terminate the test host - fixture.ThrownExceptions.Subscribe(_ => { }); - - var latestIsExecutingValue = false; - fixture.IsExecuting.Subscribe(isExec => - { - statusTrail.Add((Interlocked.Increment(ref position) - 1, $"command executing = {isExec}")); - Volatile.Write(ref latestIsExecutingValue, isExec); - }); - - // IsExecuting subscription should emit initial false value immediately with ImmediateScheduler - await Assert.That(latestIsExecutingValue).IsFalse(); - - var disposable = fixture.Execute().Subscribe(); - - await tcsStarted.Task.WaitAsync(TimeSpan.FromSeconds(2)); - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - - disposable.Dispose(); - - await tcsCaught.Task.WaitAsync(TimeSpan.FromSeconds(2)); - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - - tcsFinish.TrySetResult(Unit.Default); - - // Wait a bit for the cancellation to complete and IsExecuting to become false - await Task.Delay(100); - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsFalse(); - - using (Assert.Multiple()) - { - await Assert.That(statusTrail).IsEquivalentTo([ - (0, "command executing = False"), - (1, "command executing = True"), - (2, "started command"), - (3, "command executing = False") - ]); - } - } - - [Test] - public async Task ReactiveCommandExecutesFromInvokeCommand() - { - var tcs = new TaskCompletionSource(); - var command = ReactiveCommand.Create(() => tcs.TrySetResult(Unit.Default)); - var result = 0; - - // False, True, False - command.IsExecuting.Subscribe(_ => result++); - - Observable.Return(Unit.Default) - .InvokeCommand(command); - - await tcs.Task.WaitAsync(TimeSpan.FromSeconds(2)); - await Assert.That(result).IsGreaterThanOrEqualTo(1); - } - - [Test] - public async Task ShouldCallAsyncMethodOnSettingReactiveSetpoint() => - await new TestScheduler().WithAsync(static async scheduler => - { - // set - var fooVm = new Mocks.FooViewModel(new()); - - await Assert.That(fooVm.Foo.Value).IsEqualTo(42); - - // act - scheduler.AdvanceByMs(11); // async processing - await Assert.That(fooVm.Foo.Value).IsEqualTo(0); - - fooVm.Setpoint = 123; - scheduler.AdvanceByMs(5); // async task processing - - // assert - await Assert.That(fooVm.Foo.Value).IsEqualTo(0); - scheduler.AdvanceByMs(6); // process async setpoint setting - - await Assert.That(fooVm.Foo.Value).IsEqualTo(123); - await Task.CompletedTask; - }); - - [Test] - public async Task ReactiveCommandCreateFromTaskHandlesExecuteCancellation() - { - var tcsStarted = new TaskCompletionSource(); - var tcsCaught = new TaskCompletionSource(); - var tcsFinish = new TaskCompletionSource(); - - var statusTrail = new List<(int Position, string Status)>(); - var position = 0; - var fixture = ReactiveCommand.CreateFromTask( - async cts => - { - statusTrail.Add((Interlocked.Increment(ref position) - 1, "started command")); - tcsStarted.TrySetResult(Unit.Default); - try - { - await Task.Delay( - 10000, - cts); - } - catch (OperationCanceledException) - { - statusTrail.Add( - (Interlocked.Increment(ref position) - 1, - "starting cancelling command")); - tcsCaught.TrySetResult(Unit.Default); - await tcsFinish.Task; - statusTrail.Add( - (Interlocked.Increment(ref position) - 1, - "finished cancelling command")); - throw; - } - - return Unit.Default; - }, - outputScheduler: ImmediateScheduler.Instance); - - Exception? fail = null; - fixture.ThrownExceptions.Subscribe(ex => fail = ex); - var latestIsExecutingValue = false; - fixture.IsExecuting.Subscribe(isExec => - { - statusTrail.Add((Interlocked.Increment(ref position) - 1, $"command executing = {isExec}")); - Volatile.Write( - ref latestIsExecutingValue, - isExec); - }); - - await Assert.That(fail).IsNull(); - var result = false; - var disposable = fixture.Execute().Subscribe(_ => result = true); - - await tcsStarted.Task.WaitAsync(TimeSpan.FromSeconds(2)); - using (Assert.Multiple()) - { - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - await Assert.That(statusTrail.Any(x => x.Status == "started command")).IsTrue(); - } - - disposable.Dispose(); - - await tcsCaught.Task.WaitAsync(TimeSpan.FromSeconds(2)); - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - tcsFinish.TrySetResult(Unit.Default); - - await Task.Delay(100); - await Assert.That(fail).IsNotNull(); - - using (Assert.Multiple()) - { - await Assert.That(result).IsFalse(); - await Assert.That(statusTrail).IsEquivalentTo([ - (0, "command executing = False"), - (1, "command executing = True"), - (2, "started command"), - (3, "starting cancelling command"), - (4, "finished cancelling command"), - (5, "command executing = False") - ]); - await Assert.That(fail).IsTypeOf(); - } - } - - [Test] - public async Task ReactiveCommandCreateFromTaskHandlesTaskException() - { - var subj = new Subject(); - Exception? fail = null; - var fixture = ReactiveCommand.CreateFromTask( - async cts => - { - await subj.Take(1); - throw new Exception("break execution"); - }, - outputScheduler: ImmediateScheduler.Instance); - - fixture.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var isExecuting) - .Subscribe(); - fixture.ThrownExceptions.Subscribe(ex => fail = ex); - - using (Assert.Multiple()) - { - await Assert.That(isExecuting[0]).IsFalse(); - await Assert.That(fail).IsNull(); - } - - fixture.Execute().Subscribe(); - - using (Assert.Multiple()) - { - await Assert.That(isExecuting[1]).IsTrue(); - await Assert.That(fail).IsNull(); - } - - subj.OnNext(Unit.Default); - - // Required for correct async / await task handling - await Task.Delay(10); - - using (Assert.Multiple()) - { - await Assert.That(isExecuting).Count().IsGreaterThanOrEqualTo(3); - await Assert.That(isExecuting[2]).IsFalse(); - await Assert.That(fail?.Message).IsEqualTo("break execution"); - } - } - - [Test] - public async Task ReactiveCommandCreateFromTaskHandlesCancellation() - { - var tcsStarted = new TaskCompletionSource(); - var tcsCaught = new TaskCompletionSource(); - var tcsFinish = new TaskCompletionSource(); - - var statusTrail = new List<(int Position, string Status)>(); - var position = 0; - var fixture = ReactiveCommand.CreateFromTask( - async cts => - { - statusTrail.Add((Interlocked.Increment(ref position) - 1, "started command")); - tcsStarted.TrySetResult(Unit.Default); - try - { - await Task.Delay( - 10000, - cts); - } - catch (OperationCanceledException) - { - statusTrail.Add( - (Interlocked.Increment(ref position) - 1, - "starting cancelling command")); - tcsCaught.TrySetResult(Unit.Default); - statusTrail.Add( - (Interlocked.Increment(ref position) - 1, - "finished cancelling command")); - await tcsFinish.Task; - throw; - } - - return Unit.Default; - }, - outputScheduler: ImmediateScheduler.Instance); - - Exception? fail = null; - fixture.ThrownExceptions.Subscribe(ex => fail = ex); - var latestIsExecutingValue = false; - fixture.IsExecuting.Subscribe(isExec => - { - statusTrail.Add((Interlocked.Increment(ref position) - 1, $"command executing = {isExec}")); - Volatile.Write( - ref latestIsExecutingValue, - isExec); - }); - - await Assert.That(fail).IsNull(); - var result = false; - var disposable = fixture.Execute().Subscribe(_ => result = true); - - await tcsStarted.Task.WaitAsync(TimeSpan.FromSeconds(2)); - using (Assert.Multiple()) - { - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - await Assert.That(statusTrail.Any(x => x.Status == "started command")).IsTrue(); - } - - disposable.Dispose(); - - await tcsCaught.Task.WaitAsync(TimeSpan.FromSeconds(2)); - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - tcsFinish.TrySetResult(Unit.Default); - - await Task.Delay(100); - await Assert.That(fail).IsNotNull(); - - using (Assert.Multiple()) - { - await Assert.That(result).IsFalse(); - await Assert.That(statusTrail).IsEquivalentTo([ - (0, "command executing = False"), - (1, "command executing = True"), - (2, "started command"), - (3, "starting cancelling command"), - (4, "finished cancelling command"), - (5, "command executing = False") - ]); - await Assert.That(fail).IsTypeOf(); - } - } - - [Test] - public async Task ReactiveCommandCreateFromTaskHandlesCompletion() - { - var tcsStarted = new TaskCompletionSource(); - var tcsFinished = new TaskCompletionSource(); - var tcsContinue = new TaskCompletionSource(); - - var statusTrail = new List<(int Position, string Status)>(); - var position = 0; - var fixture = ReactiveCommand.CreateFromTask( - async cts => - { - statusTrail.Add((Interlocked.Increment(ref position) - 1, "started command")); - tcsStarted.TrySetResult(Unit.Default); - try - { - await Task.Delay( - 1000, - cts); - } - catch (OperationCanceledException) - { - statusTrail.Add( - (Interlocked.Increment(ref position) - 1, - "starting cancelling command")); - await Task.Delay( - 5000, - CancellationToken.None); - statusTrail.Add( - (Interlocked.Increment(ref position) - 1, - "finished cancelling command")); - throw; - } - - statusTrail.Add((Interlocked.Increment(ref position) - 1, "finished command")); - tcsFinished.TrySetResult(Unit.Default); - await tcsContinue.Task; - return Unit.Default; - }, - outputScheduler: ImmediateScheduler.Instance); - - Exception? fail = null; - fixture.ThrownExceptions.Subscribe(ex => fail = ex); - var latestIsExecutingValue = false; - fixture.IsExecuting.Subscribe(isExec => - { - statusTrail.Add((Interlocked.Increment(ref position) - 1, $"command executing = {isExec}")); - Volatile.Write( - ref latestIsExecutingValue, - isExec); - }); - - await Assert.That(fail).IsNull(); - var result = false; - fixture.Execute().Subscribe(_ => result = true); - - await tcsStarted.Task.WaitAsync(TimeSpan.FromSeconds(2)); - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - - await tcsFinished.Task.WaitAsync(TimeSpan.FromSeconds(2)); - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - tcsContinue.TrySetResult(Unit.Default); - - await Task.Delay(100); - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsFalse(); - - using (Assert.Multiple()) - { - await Assert.That(result).IsTrue(); - await Assert.That(statusTrail).IsEquivalentTo([ - (0, "command executing = False"), - (1, "command executing = True"), - (2, "started command"), - (3, "finished command"), - (4, "command executing = False") - ]); - await Assert.That(fail).IsNull(); - await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsFalse(); - } - } - - [Test] - public async Task CreateRunInBackground_With_TParam_Uses_BackgroundScheduler_When_Provided() - { - var executed = false; - var backgroundScheduler = ImmediateScheduler.Instance; - var fixture = ReactiveCommand.CreateRunInBackground( - param => executed = true, - backgroundScheduler: backgroundScheduler, - outputScheduler: ImmediateScheduler.Instance); - - await fixture.Execute(42); - await Assert.That(executed).IsTrue(); - } - - [Test] - public async Task CreateRunInBackground_With_TParam_TResult_Uses_BackgroundScheduler_When_Provided() - { - var backgroundScheduler = ImmediateScheduler.Instance; - var fixture = ReactiveCommand.CreateRunInBackground( - param => param.ToString(), - backgroundScheduler: backgroundScheduler, - outputScheduler: ImmediateScheduler.Instance); - fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); - - await fixture.Execute(42); - - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo("42"); - } - - public void Dispose() - { - _schedulersScope?.Dispose(); - _schedulersScope = null; - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Expression/ReflectionTypeEqualityTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/Expression/ReflectionTypeEqualityTests.cs deleted file mode 100644 index fe41cad753..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Expression/ReflectionTypeEqualityTests.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -public class ReflectionTypeEqualityTests -{ - [Test] - public async Task AssemblyGetTypes_ContainsTypeWithMatchingAQN() - { - // This test verifies that assembly.GetTypes() returns a type with the same AQN as typeof() - var typeFromTypeof = typeof(FooView); - var aqnFromTypeof = typeFromTypeof.AssemblyQualifiedName!; - - // Search through all loaded assemblies - bool found = false; - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - try - { - foreach (var t in assembly.GetTypes()) - { - if (t.AssemblyQualifiedName == aqnFromTypeof) - { - found = true; - break; - } - } - - if (found) - { - break; - } - } - catch - { - // Continue - } - } - - await Assert.That(found).IsTrue(); - } - - [Test] - public async Task ReallyFindType_ShouldReturn_SameInstanceAs_Typeof_ForSimpleType() - { - // Arrange - var typeFromTypeof = typeof(FooView); - var assemblyQualifiedName = typeFromTypeof.AssemblyQualifiedName!; - - // Act - var typeFromReflection = Reflection.ReallyFindType(assemblyQualifiedName, false); - - // Assert - using (Assert.Multiple()) - { - await Assert.That(typeFromReflection).IsNotNull(); - await Assert.That(typeFromReflection).IsEqualTo(typeFromTypeof); - await Assert.That(ReferenceEquals(typeFromReflection, typeFromTypeof)).IsTrue(); - } - } - - [Test] - public async Task ReallyFindType_ShouldReturn_SameInstanceAs_Typeof_ForGenericInterfaceType() - { - // Arrange - var typeFromTypeof = typeof(IViewFor); - var assemblyQualifiedName = typeFromTypeof.AssemblyQualifiedName!; - - // Act - var typeFromReflection = Reflection.ReallyFindType(assemblyQualifiedName, false); - - // Assert - using (Assert.Multiple()) - { - await Assert.That(typeFromReflection).IsNotNull(); - await Assert.That(typeFromReflection).IsEqualTo(typeFromTypeof); - await Assert.That(ReferenceEquals(typeFromReflection, typeFromTypeof)).IsTrue(); - } - } - - [Test] - public async Task MakeGenericType_ShouldReturn_SameInstanceAs_Typeof() - { - // This tests the hypothesis that MakeGenericType returns the same instance as typeof - var typeFromTypeof = typeof(IViewFor); - var typeFromMakeGeneric = typeof(IViewFor<>).MakeGenericType(typeof(FooViewModel)); - - using (Assert.Multiple()) - { - await Assert.That(typeFromMakeGeneric).IsEqualTo(typeFromTypeof); - await Assert.That(ReferenceEquals(typeFromMakeGeneric, typeFromTypeof)).IsTrue(); - } - } - - [Test] - public async Task ReallyFindType_ShouldReturn_SameInstanceAs_MakeGenericType() - { - // Arrange - var typeFromMakeGeneric = typeof(IViewFor<>).MakeGenericType(typeof(FooViewModel)); - var assemblyQualifiedName = typeFromMakeGeneric.AssemblyQualifiedName!; - - // Act - var typeFromReflection = Reflection.ReallyFindType(assemblyQualifiedName, false); - - // Assert - using (Assert.Multiple()) - { - await Assert.That(typeFromReflection).IsNotNull(); - await Assert.That(typeFromReflection).IsEqualTo(typeFromMakeGeneric); - await Assert.That(ReferenceEquals(typeFromReflection, typeFromMakeGeneric)).IsTrue(); - } - } - - [Test] - public async Task ModernDependencyResolver_ShouldFind_ServiceRegisteredWith_Typeof_WhenLookedUpWith_ReallyFindType() - { - // Arrange - var resolver = new ModernDependencyResolver(); - var serviceType = typeof(IViewFor); - - // Register using typeof - resolver.Register(() => new FooView(), serviceType); - - // Act - lookup using ReallyFindType - var assemblyQualifiedName = serviceType.AssemblyQualifiedName!; - var typeFromReflection = Reflection.ReallyFindType(assemblyQualifiedName, false); - - await Assert.That(typeFromReflection).IsNotNull(); - - var service = resolver.GetService(typeFromReflection!); - - // Assert - using (Assert.Multiple()) - { - await Assert.That(service).IsNotNull(); - await Assert.That(service).IsTypeOf(); - } - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/DefaultViewLocatorTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/DefaultViewLocatorTests.cs deleted file mode 100644 index 2ac635b670..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/DefaultViewLocatorTests.cs +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright (c) 2025 .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.Assertions; -using TUnit.Assertions.Extensions; -using TUnit.Core; - -using static TUnit.Assertions.Assert; - -namespace ReactiveUI.Tests.Core; - -/// -/// Tests for the class. -/// -[NotInParallel] -public partial class DefaultViewLocatorTests -{ - /// - /// Diagnostic test to verify registration and resolution. - /// - /// A representing the asynchronous operation. - [Test] - public async Task DiagnosticTestForRegistrationAndResolution() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - // Register - resolver.Register(() => new FooView(), typeof(IViewFor)); - - // Verify registration - var hasReg = resolver.HasRegistration(typeof(IViewFor)); - await That(hasReg).IsTrue(); - - using (resolver.WithResolver()) - { - // Test direct GetService - var service = AppLocator.Current.GetService(typeof(IViewFor)); - await That(service).IsNotNull(); - await That(service).IsTypeOf(); - - // Test that the ViewLocator can find the view by manually checking the type - var vmType = typeof(FooViewModel); - var expectedViewForType = typeof(IViewFor); - var manualService = AppLocator.Current.GetService(expectedViewForType); - await That(manualService).IsNotNull(); - - // Test through ViewLocator - var fixture = new DefaultViewLocator(); - var vm = new FooViewModel(); - var result = fixture.ResolveView(vm); - await That(result).IsNotNull(); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that the default name of the view model is replaced with view when determining the service. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ByDefaultViewModelIsReplacedWithViewWhenDeterminingTheServiceName() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooView(), typeof(IViewFor)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - var vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that the runtime type of the view model is used to resolve the view. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TheRuntimeTypeOfTheViewModelIsUsedToResolveTheView() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooView(), typeof(FooView)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - object vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that the view model to view naming convention can be customized. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ViewModelToViewNamingConventionCanBeCustomized() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooWithWeirdConvention(), typeof(FooWithWeirdConvention)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator - { - ViewModelToViewFunc = - static viewModelName => viewModelName.Replace("ViewModel", "WithWeirdConvention") - }; - var vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that makes sure that this instance [can resolve view from view model class using class registration]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanResolveViewFromViewModelClassUsingClassRegistration() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooView(), typeof(FooView)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - var vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that make sure this instance [can resolve view from view model class using interface registration]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanResolveViewFromViewModelClassUsingInterfaceRegistration() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooView(), typeof(IFooView)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - var vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - } - } - - /// - /// Test that makes sure that this instance [can resolve view from view model class using IView for registration]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanResolveViewFromViewModelClassUsingIViewForRegistration() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooView(), typeof(IViewFor)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - var vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that this instance [can resolve view from view model interface using class registration]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanResolveViewFromViewModelInterfaceUsingClassRegistration() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooView(), typeof(FooView)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - IFooViewModel vm = new FooViewModelWithWeirdName(); - - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that this instance [can resolve view from view model interface using interface registration]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanResolveViewFromViewModelInterfaceUsingInterfaceRegistration() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooView(), typeof(IFooView)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - IFooViewModel vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that this instance [can resolve view from view model interface using i view for registration]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanResolveViewFromViewModelInterfaceUsingIViewForRegistration() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooView(), typeof(IViewFor)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - IFooViewModel vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that contracts is used when resolving view. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ContractIsUsedWhenResolvingView() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new FooView(), typeof(IViewFor), "first"); - resolver.Register(static () => new FooWithWeirdConvention(), typeof(IViewFor), "second"); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - var vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsNull(); - - result = fixture.ResolveView(vm, "first"); - await That(result).IsTypeOf(); - - result = fixture.ResolveView(vm, "second"); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that no errors are raised if a type cannot be found. - /// - /// A representing the asynchronous operation. - [Test] - public async Task NoErrorIsRaisedIfATypeCannotBeFound() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator - { - ViewModelToViewFunc = static viewModelName => - "DoesNotExist, " + typeof(DefaultViewLocatorTests).Assembly.FullName - }; - var vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsNull(); - } - } - - /// - /// Tests that no errors are raised if a service cannot be found. - /// - /// A representing the asynchronous operation. - [Test] - public async Task NoErrorIsRaisedIfAServiceCannotBeFound() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - var vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsNull(); - } - } - - /// - /// Tests that no errors are raised if the service does not implement IViewFor. - /// - /// A representing the asynchronous operation. - [Test] - public async Task NoErrorIsRaisedIfTheServiceDoesNotImplementIViewFor() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => "this string does not implement IViewFor", typeof(IViewFor)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - var vm = new FooViewModel(); - - var result = fixture.ResolveView(vm); - await That(result).IsNull(); - } - } - - /// - /// Tests that an exception is thrown if the creation of the view fails. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExceptionIsThrownIfTheCreationOfTheViewFails() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(() => new FooThatThrowsView(), typeof(IViewFor)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - var vm = new FooViewModel(); - - var ex = await ThrowsAsync(async () => - { - fixture.ResolveView(vm); - await Task.CompletedTask; - }); - - await That(ex!.Message).IsEqualTo("This is a test failure."); - } - } - - /// - /// Tests that with odd interface name doesnt throw exception. - /// - [Test] - public void WithOddInterfaceNameDoesntThrowException() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - - var vm = new StrangeClassNotFollowingConvention(); - - fixture.ResolveView((IStrangeInterfaceNotFollowingConvention)vm); - } - } - - /// - /// Tests that AOT mapping with Map method resolves views correctly. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AotMapping_WithMapMethod_ResolvesViewCorrectly() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - fixture.Map(() => new FooViewForConcreteType()); - - var vm = new FooViewModel(); - var result = fixture.ResolveView(vm); - - await That(result).IsNotNull(); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that AOT mapping with contract resolves correct view. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AotMapping_WithContract_ResolvesCorrectView() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - fixture.Map(() => new FooViewForConcreteType(), "contract1") - .Map(() => new AlternateFooView(), "contract2"); - - var vm = new FooViewModel(); - - var result1 = fixture.ResolveView(vm, "contract1"); - await That(result1).IsTypeOf(); - - var result2 = fixture.ResolveView(vm, "contract2"); - await That(result2).IsTypeOf(); - } - } - - /// - /// Tests that AOT mapping with default contract is used when specific contract not found. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AotMapping_FallsBackToDefaultContract_WhenSpecificNotFound() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - fixture.Map(() => new FooViewForConcreteType()); - - var vm = new FooViewModel(); - var result = fixture.ResolveView(vm, "nonexistent"); - - await That(result).IsNotNull(); - await That(result).IsTypeOf(); - } - } - - /// - /// Tests that Unmap removes AOT mapping. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Unmap_RemovesAotMapping() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - fixture.Map(() => new FooViewForConcreteType()); - - var vm = new FooViewModel(); - var result = fixture.ResolveView(vm); - await That(result).IsTypeOf(); - - fixture.Unmap(); - result = fixture.ResolveView(vm); - await That(result).IsNull(); - } - } - - /// - /// Tests that Unmap with contract removes only that contract mapping. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Unmap_WithContract_RemovesOnlyThatMapping() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - fixture.Map(() => new FooViewForConcreteType()) - .Map(() => new AlternateFooView(), "contract1"); - - var vm = new FooViewModel(); - - fixture.Unmap("contract1"); - - var defaultResult = fixture.ResolveView(vm); - await That(defaultResult).IsTypeOf(); - - var contract1Result = fixture.ResolveView(vm, "contract1"); - await That(contract1Result).IsTypeOf(); - } - } - - /// - /// Test view that implements IViewFor for FooViewModel specifically. - /// - private class FooViewForConcreteType : IViewFor - { - /// - object? IViewFor.ViewModel { get; set; } - - /// - public FooViewModel? ViewModel { get; set; } - } - - /// - /// Another test view for testing contract-based AOT mapping. - /// - private class AlternateFooView : IViewFor - { - /// - object? IViewFor.ViewModel { get; set; } - - /// - public FooViewModel? ViewModel { get; set; } - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/BarView.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/BarView.cs deleted file mode 100644 index 92e0740136..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/BarView.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A mock view. -/// -public class BarView : IViewFor -{ - /// - /// 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 = (IBarViewModel?)value; - } - - /// - /// Gets or sets the ViewModel corresponding to this specific View. This should be - /// a DependencyProperty if you're using XAML. - /// - public IBarViewModel? ViewModel { get; set; } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/BarViewModel.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/BarViewModel.cs deleted file mode 100644 index 59183f3bf4..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/BarViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A mock view. -/// -public class BarViewModel : ReactiveObject, IBarViewModel; diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooThatThrowsView.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooThatThrowsView.cs deleted file mode 100644 index f2102cbfeb..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooThatThrowsView.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A mock view which throws. -/// -[ExcludeFromViewRegistration] -public class FooThatThrowsView : IFooView -{ - /// - /// Initializes a new instance of the class. - /// - /// This is a test failure. - public FooThatThrowsView() => throw new InvalidOperationException("This is a test failure."); - - /// - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (IFooViewModel?)value; - } - - /// - public IFooViewModel? ViewModel { get; set; } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooView.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooView.cs deleted file mode 100644 index fc052081e0..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooView.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A mock view. -/// -public class FooView : IFooView -{ - /// - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (IFooViewModel?)value; - } - - /// - public IFooViewModel? ViewModel { get; set; } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooViewModel.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooViewModel.cs deleted file mode 100644 index e62ca43156..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A mock view model. -/// -public class FooViewModel : ReactiveObject, IFooViewModel; diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooViewModelWithWeirdName.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooViewModelWithWeirdName.cs deleted file mode 100644 index f07daf0c4f..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooViewModelWithWeirdName.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A mock view model. -/// -public class FooViewModelWithWeirdName : ReactiveObject, IFooViewModel; diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooWithWeirdConvention.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooWithWeirdConvention.cs deleted file mode 100644 index 36310f97e4..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/FooWithWeirdConvention.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A mock view. -/// -public class FooWithWeirdConvention : IFooView -{ - /// - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (IFooViewModel?)value; - } - - /// - public IFooViewModel? ViewModel { get; set; } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IBarViewModel.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IBarViewModel.cs deleted file mode 100644 index d40c37ddb0..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IBarViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A mock interface view model. -/// -public interface IBarViewModel; diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IFooView.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IFooView.cs deleted file mode 100644 index 35192dff2e..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IFooView.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A interface mock view. -/// -public interface IFooView : IViewFor; diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IStrangeInterfaceNotFollowingConvention.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IStrangeInterfaceNotFollowingConvention.cs deleted file mode 100644 index 98c447f21c..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IStrangeInterfaceNotFollowingConvention.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A strange interface. -/// -public interface IStrangeInterfaceNotFollowingConvention; diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/StrangeClassNotFollowingConvention.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/StrangeClassNotFollowingConvention.cs deleted file mode 100644 index d5426fce17..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/StrangeClassNotFollowingConvention.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -/// -/// A strange class. -/// -public class StrangeClassNotFollowingConvention : IStrangeInterfaceNotFollowingConvention; diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/ViewLocatorTest.cs b/src/tests/ReactiveUI.NonParallel.Tests/Locator/ViewLocatorTest.cs deleted file mode 100644 index 4d3a6032c2..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/ViewLocatorTest.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2025 .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.Assertions; -using TUnit.Assertions.Extensions; -using TUnit.Core; - -using static TUnit.Assertions.Assert; - -namespace ReactiveUI.Tests.Core; - -/// -/// Tests for the static class. -/// -[NotInParallel] -public class ViewLocatorTest -{ - /// - /// Tests that ViewLocator.Current throws when no locator is registered. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Current_ThrowsViewLocatorNotFoundException_WhenNoLocatorRegistered() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - - // Don't initialize ReactiveUI - this will leave no IViewLocator registered - using (resolver.WithResolver()) - { - var ex = await ThrowsAsync(async () => - { - _ = ViewLocator.Current; - await Task.CompletedTask; - }); - - await That(ex).IsNotNull(); - await That(ex!.Message).Contains("Could not find a default ViewLocator"); - } - } - - /// - /// Tests that ViewLocator.Current returns the registered locator when available. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Current_ReturnsRegisteredLocator_WhenLocatorIsRegistered() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - var locator = ViewLocator.Current; - - await That(locator).IsNotNull(); - await That(locator).IsTypeOf(); - } - } - - /// - /// Tests that ViewLocator.Current returns custom locator when registered. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Current_ReturnsCustomLocator_WhenCustomLocatorIsRegistered() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - - var customLocator = new CustomViewLocator(); - resolver.Register(() => customLocator, typeof(IViewLocator)); - - using (resolver.WithResolver()) - { - var locator = ViewLocator.Current; - - await That(locator).IsNotNull(); - await That(locator).IsSameReferenceAs(customLocator); - } - } - - /// - /// Custom view locator for testing. - /// - private class CustomViewLocator : IViewLocator - { - /// - public IViewFor? ResolveView(T? viewModel, string? contract = null) => null; - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/MessageBusExtensionsTest.cs b/src/tests/ReactiveUI.NonParallel.Tests/MessageBusExtensionsTest.cs deleted file mode 100644 index 6622d399a4..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/MessageBusExtensionsTest.cs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2025 .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.Testing; -using ReactiveUI.Tests.Infrastructure.StaticState; - -namespace ReactiveUI.Tests; - -/// -/// Tests for . -/// -[NotInParallel] -public class MessageBusExtensionsTest -{ - private MessageBusScope? _messageBusScope; - - [Before(Test)] - public void SetUp() - { - _messageBusScope = new MessageBusScope(); - } - - [After(Test)] - public void TearDown() - { - _messageBusScope?.Dispose(); - } - - /// - /// Tests that WithMessageBus overrides the message bus. - /// - /// A representing the asynchronous operation. - [Test] - public async Task WithMessageBus_OverridesMessageBus() - { - var originalBus = MessageBus.Current; - var testBus = new MessageBus(); - - using (testBus.WithMessageBus()) - { - await Assert.That(MessageBus.Current).IsEqualTo(testBus); - } - - await Assert.That(MessageBus.Current).IsEqualTo(originalBus); - } - - /// - /// Tests that With action executes the action. - /// - /// A representing the asynchronous operation. - [Test] - public async Task With_Action_ExecutesAction() - { - var testBus = new MessageBus(); - var executed = false; - - testBus.With(() => executed = true); - - await Assert.That(executed).IsTrue(); - } - - /// - /// Tests that With function returns the result. - /// - /// A representing the asynchronous operation. - [Test] - public async Task With_Function_ReturnsResult() - { - var testBus = new MessageBus(); - - var result = testBus.With(() => 42); - - await Assert.That(result).IsEqualTo(42); - } - - /// - /// Tests that With function throws for null function. - /// - /// A representing the asynchronous operation. - [Test] - public async Task With_NullFunction_Throws() - { - var testBus = new MessageBus(); - - await Assert.That(() => testBus.With((Func)null!)) - .Throws(); - } - - /// - /// Tests that With action throws for null action. - /// - /// A representing the asynchronous operation. - [Test] - public async Task With_NullAction_Throws() - { - var testBus = new MessageBus(); - - await Assert.That(() => testBus.With((Action)null!)) - .Throws(); - } - - /// - /// Tests that With function overrides message bus during execution. - /// - /// A representing the asynchronous operation. - [Test] - public async Task With_Function_OverridesMessageBusDuringExecution() - { - var originalBus = MessageBus.Current; - var testBus = new MessageBus(); - IMessageBus? capturedBus = null; - - testBus.With(() => - { - capturedBus = MessageBus.Current; - return 42; - }); - - await Assert.That(capturedBus).IsEqualTo(testBus); - await Assert.That(MessageBus.Current).IsEqualTo(originalBus); - } - - /// - /// Tests that With action overrides message bus during execution. - /// - /// A representing the asynchronous operation. - [Test] - public async Task With_Action_OverridesMessageBusDuringExecution() - { - var originalBus = MessageBus.Current; - var testBus = new MessageBus(); - IMessageBus? capturedBus = null; - - testBus.With(() => - { - capturedBus = MessageBus.Current; - }); - - await Assert.That(capturedBus).IsEqualTo(testBus); - await Assert.That(MessageBus.Current).IsEqualTo(originalBus); - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/MessageBusTest.cs b/src/tests/ReactiveUI.NonParallel.Tests/MessageBusTest.cs deleted file mode 100644 index 2a902edb66..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/MessageBusTest.cs +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright (c) 2025 .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 DynamicData; - -using Microsoft.Reactive.Testing; - -using ReactiveUI.Testing; -using ReactiveUI.Tests.Infrastructure; -using ReactiveUI.Tests.Infrastructure.StaticState; - -namespace ReactiveUI.Tests.Core; - -[NotInParallel] -public class MessageBusTest : IDisposable -{ - private MessageBusScope? _messageBusScope; - private LocatorScope? _locatorScope; - - [Before(Test)] - public void SetUp() - { - _locatorScope = new LocatorScope(); - _messageBusScope = new MessageBusScope(); - } - - [After(Test)] - public void TearDown() - { - _messageBusScope?.Dispose(); - _locatorScope?.Dispose(); - } - - /// - /// Smoke tests the MessageBus. - /// - /// A representing the asynchronous operation. - [Test] - public async Task MessageBusSmokeTest() - { - var input = new[] { 1, 2, 3, 4 }; - - var result = await new TestScheduler().With(async scheduler => - { - var source = new Subject(); - var fixture = new MessageBus(); - - fixture.RegisterMessageSource(source, "Test"); - using (Assert.Multiple()) - { - await Assert.That(fixture.IsRegistered(typeof(int))).IsFalse(); - await Assert.That(fixture.IsRegistered(typeof(int), "Foo")).IsFalse(); - } - - fixture.Listen("Test").ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var output).Subscribe(); - - input.Run(source.OnNext); - - scheduler.Start(); - return output; - }); - - await Assert.That(result).IsEquivalentTo(input); - } - - /// - /// Tests that explicits send message should work even after registering source. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExplicitSendMessageShouldWorkEvenAfterRegisteringSource() - { - Locator.CurrentMutable.InitializeSplat(); - Locator.CurrentMutable.InitializeReactiveUI(); - var fixture = new MessageBus(); - fixture.RegisterMessageSource(Observable.Never); - - var messageReceived = false; - fixture.Listen().Subscribe(_ => messageReceived = true); - - fixture.SendMessage(42); - await Assert.That(messageReceived).IsTrue(); - } - - /// - /// Tests that listening before registering a source should work. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ListeningBeforeRegisteringASourceShouldWork() - { - var fixture = new MessageBus(); - var result = -1; - - fixture.Listen().Subscribe(x => result = x); - - await Assert.That(result).IsEqualTo(-1); - - fixture.SendMessage(42); - - await Assert.That(result).IsEqualTo(42); - } - - /// - /// Tests that the Garbage Collector should not kill message service. - /// - /// A representing the asynchronous operation. - [Test] - public async Task GcShouldNotKillMessageService() - { - var bus = new MessageBus(); - - var receivedMessage = false; - var dispose = bus.Listen().Subscribe(_ => receivedMessage = true); - bus.SendMessage(1); - await Assert.That(receivedMessage).IsTrue(); - - GC.Collect(); - GC.WaitForPendingFinalizers(); - - receivedMessage = false; - bus.SendMessage(2); - await Assert.That(receivedMessage).IsTrue(); - } - - /// - /// Tests that Registering the second message source should merge both sources. - /// - /// A representing the asynchronous operation. - [Test] - public async Task RegisteringSecondMessageSourceShouldMergeBothSources() - { - var bus = new MessageBus(); - var source1 = new Subject(); - var source2 = new Subject(); - var receivedMessage1 = false; - var receivedMessage2 = false; - - bus.RegisterMessageSource(source1); - bus.Listen().Subscribe(_ => receivedMessage1 = true); - - bus.RegisterMessageSource(source2); - bus.Listen().Subscribe(_ => receivedMessage2 = true); - - source1.OnNext(1); - using (Assert.Multiple()) - { - await Assert.That(receivedMessage1).IsTrue(); - await Assert.That(receivedMessage2).IsTrue(); - } - - receivedMessage1 = false; - receivedMessage2 = false; - - source2.OnNext(2); - using (Assert.Multiple()) - { - await Assert.That(receivedMessage1).IsTrue(); - await Assert.That(receivedMessage2).IsTrue(); - } - } - - /// - /// Tests the MessageBus threading. - /// - /// A representing the asynchronous operation. - [Test] - public async Task MessageBusThreadingTest() - { - Locator.CurrentMutable.InitializeSplat(); - Locator.CurrentMutable.InitializeReactiveUI(); - var messageBus = new MessageBus(); - messageBus.RegisterScheduler(ImmediateScheduler.Instance); - int? listenedThreadId = null; - int? otherThreadId = null; - var thisThreadId = Environment.CurrentManagedThreadId; - - // Use LongRunning to force a dedicated thread instead of ThreadPool thread reuse - await Task.Factory.StartNew( - () => - { - otherThreadId = Environment.CurrentManagedThreadId; - messageBus.Listen().Subscribe(_ => listenedThreadId = Environment.CurrentManagedThreadId); - messageBus.SendMessage(42); - }, - CancellationToken.None, - TaskCreationOptions.LongRunning, - TaskScheduler.Default); - - using (Assert.Multiple()) - { - await Assert.That(thisThreadId).IsNotEqualTo(listenedThreadId!.Value); - await Assert.That(otherThreadId!.Value).IsEqualTo(listenedThreadId.Value); - } - } - - /// - /// Tests MessageBus.RegisterScheduler method for complete coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task MessageBus_RegisterScheduler_ShouldWork() - { - // Arrange - var messageBus = new MessageBus(); - var receivedMessages = new List(); - - // Act - Register scheduler without contract first - messageBus.RegisterScheduler(CurrentThreadScheduler.Instance); - messageBus.Listen().Subscribe(x => receivedMessages.Add(x)); - messageBus.SendMessage(42); - - // Assert - await Assert.That(receivedMessages).Count().IsEqualTo(1); - await Assert.That(receivedMessages[0]).IsEqualTo(42); - } - - /// - /// Tests MessageBus.ListenIncludeLatest method for complete coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task MessageBus_ListenIncludeLatest_ShouldIncludeLastMessage() - { - // Arrange - var messageBus = new MessageBus(); - var receivedMessages = new List(); - - // Send a message first - messageBus.SendMessage(42); - - // Act - Listen including latest should get the previously sent message - messageBus.ListenIncludeLatest().Subscribe(x => receivedMessages.Add(x)); - - // Assert - await Assert.That(receivedMessages).Count().IsEqualTo(1); - await Assert.That(receivedMessages[0]).IsEqualTo(42); - } - - /// - /// Tests MessageBus.Current static property for complete coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task MessageBus_Current_ShouldBeAccessible() - { - // Act - var current = MessageBus.Current; - - // Assert - await Assert.That(current).IsNotNull(); - await Assert.That(current).IsAssignableTo(); - } - - /// - /// Tests MessageBus with contracts to ensure message isolation. - /// - /// A representing the asynchronous operation. - [Test] - public async Task MessageBus_WithContracts_ShouldIsolateMessages() - { - // Arrange - var messageBus = new MessageBus(); - var contract1Messages = new List(); - var contract2Messages = new List(); - var noContractMessages = new List(); - - // Act - messageBus.Listen("Contract1").Subscribe(x => contract1Messages.Add(x)); - messageBus.Listen("Contract2").Subscribe(x => contract2Messages.Add(x)); - messageBus.Listen().Subscribe(x => noContractMessages.Add(x)); - - messageBus.SendMessage(1, "Contract1"); - messageBus.SendMessage(2, "Contract2"); - messageBus.SendMessage(3); - - // Assert - await Assert.That(contract1Messages).Count().IsEqualTo(1); - using (Assert.Multiple()) - { - await Assert.That(contract1Messages[0]).IsEqualTo(1); - - await Assert.That(contract2Messages).Count().IsEqualTo(1); - } - - using (Assert.Multiple()) - { - await Assert.That(contract2Messages[0]).IsEqualTo(2); - - await Assert.That(noContractMessages).Count().IsEqualTo(1); - } - - await Assert.That(noContractMessages[0]).IsEqualTo(3); - } - - public void Dispose() - { - _messageBusScope?.Dispose(); - _messageBusScope = null; - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Mixins/LoggingRegistrationScope.cs b/src/tests/ReactiveUI.NonParallel.Tests/Mixins/LoggingRegistrationScope.cs deleted file mode 100644 index 6e67e6d532..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Mixins/LoggingRegistrationScope.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2025 .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.Mixins; - -internal class LoggingRegistrationScope : IDisposable -{ - private static IDisposable? _logManagerRegistration; - private ILogManager? _previousLogManager; - private TestLogger _logger; - private IFullLogger _fullLogger; - - public LoggingRegistrationScope() - { - // Save the current ILogManager if one exists - _previousLogManager = Locator.Current.GetService(); - - _logger = new TestLogger(); - _fullLogger = new WrappingFullLogger(_logger); - - // Register a default ILogManager for tests - // This ensures ILogManager is available even if tests don't set up their own - var currentLogManager = new TestLogManager(_fullLogger); - Locator.CurrentMutable.Register(() => currentLogManager); - } - - internal TestLogger Logger => _logger; - - public void Dispose() - { - // Dispose of the log manager registration - _logManagerRegistration?.Dispose(); - _logManagerRegistration = null; - - // Restore the previous ILogManager if there was one - // Note: We can't easily unregister, so we just re-register the old one if it existed - if (_previousLogManager != null) - { - Locator.CurrentMutable.Register(() => _previousLogManager); - } - } - - internal class TestLogger : ILogger - { - public TestLogger() - { - Messages = []; - Level = LogLevel.Debug; - } - - public List<(string message, Type type, LogLevel logLevel)> Messages { get; } - - /// - public LogLevel Level { get; set; } - - /// - public void Write(Exception exception, string message, Type type, LogLevel logLevel) => Messages.Add((message, typeof(TestLogger), logLevel)); - - /// - public void Write(string message, LogLevel logLevel) => Messages.Add((message, typeof(TestLogger), logLevel)); - - /// - public void Write(Exception exception, string message, LogLevel logLevel) => Messages.Add((message, typeof(TestLogger), logLevel)); - - /// - public void Write([Localizable(false)] string message, [Localizable(false)] Type type, LogLevel logLevel) => Messages.Add((message, type, logLevel)); - } - - private class TestLogManager(IFullLogger logger) : ILogManager - { - public IFullLogger GetLogger(Type type) => logger; - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/PlatformRegistrationManagerTest.cs b/src/tests/ReactiveUI.NonParallel.Tests/PlatformRegistrationManagerTest.cs deleted file mode 100644 index 8a339e8fea..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/PlatformRegistrationManagerTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2025 .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; - -/// -/// Tests for . -/// -[NotInParallel] -public class PlatformRegistrationManagerTest -{ - /// - /// Tests that SetRegistrationNamespaces sets the namespaces. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SetRegistrationNamespaces_SetsNamespaces() - { - var originalNamespaces = PlatformRegistrationManager.NamespacesToRegister; - var newNamespaces = new[] { RegistrationNamespace.Maui }; - - try - { - PlatformRegistrationManager.SetRegistrationNamespaces(newNamespaces); - - await Assert.That(PlatformRegistrationManager.NamespacesToRegister).IsEquivalentTo(newNamespaces); - } - finally - { - // Restore original namespaces - PlatformRegistrationManager.SetRegistrationNamespaces(originalNamespaces); - } - } - - /// - /// Tests that DefaultRegistrationNamespaces is not null. - /// - /// A representing the asynchronous operation. - [Test] - public async Task DefaultRegistrationNamespaces_IsNotNull() - { - await Assert.That(PlatformRegistrationManager.DefaultRegistrationNamespaces).IsNotNull(); - await Assert.That(PlatformRegistrationManager.DefaultRegistrationNamespaces).Count().IsGreaterThan(0); - } - - /// - /// Tests that NamespacesToRegister starts with default namespaces. - /// - /// A representing the asynchronous operation. - [Test] - public async Task NamespacesToRegister_StartsWithDefaultNamespaces() - { - await Assert.That(PlatformRegistrationManager.NamespacesToRegister).IsNotNull(); - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/windows-xaml/XamlViewDependencyResolverTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/windows-xaml/XamlViewDependencyResolverTests.cs deleted file mode 100644 index e92ff86336..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/windows-xaml/XamlViewDependencyResolverTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2025 .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.Tests.Xaml; - -/// -/// Tests associated with UI and the . -/// -public sealed class XamlViewDependencyResolverTests : IDisposable -{ - private readonly IDependencyResolver _resolver; - - /// - /// Initializes a new instance of the class. - /// - public XamlViewDependencyResolverTests() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.RegisterViewsForViewModels(GetType().Assembly); - - _resolver = resolver; - } - - /// - /// Test that register views for view model should register all views. - /// - /// A representing the asynchronous operation. - [Test] - [TestExecutor] - public async Task RegisterViewsForViewModelShouldRegisterAllViews() - { - using (_resolver.WithResolver()) - using (Assert.Multiple()) - { - await Assert.That(_resolver.GetServices>()).Count().IsEqualTo(1); - await Assert.That(_resolver.GetServices>()).Count().IsEqualTo(1); - await Assert.That(_resolver.GetServices>()).Count().IsEqualTo(1); - await Assert.That(_resolver.GetServices>()).Count().IsEqualTo(1); - } - } - - /// - /// Test that register views for view model should include contracts. - /// - /// A representing the asynchronous operation. - [Test] - [TestExecutor] - public async Task RegisterViewsForViewModelShouldIncludeContracts() - { - using (_resolver.WithResolver()) - { - await Assert.That(_resolver.GetServices(typeof(IViewFor), "contract")).Count().IsEqualTo(1); - } - } - - /// - public void Dispose() => _resolver.Dispose(); -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet6_0.verified.txt b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet6_0.verified.txt deleted file mode 100644 index 5d90003771..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet6_0.verified.txt +++ /dev/null @@ -1,116 +0,0 @@ -[assembly: System.Runtime.Versioning.SupportedOSPlatform("Windows10.0.17763.0")] -[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName=".NET 6.0")] -[assembly: System.Runtime.Versioning.TargetPlatform("Windows10.0.17763.0")] -namespace ReactiveUI.Winforms -{ - public class ActivationForViewFetcher : ReactiveUI.IActivationForViewFetcher, Splat.IEnableLogger - { - public ActivationForViewFetcher() { } - public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } - public int GetAffinityForView(System.Type view) { } - } - public class ContentControlBindingHook : ReactiveUI.IPropertyBindingHook - { - public ContentControlBindingHook() { } - public bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction) { } - } - public class CreatesWinformsCommandBinding : ReactiveUI.ICreatesCommandBinding - { - public CreatesWinformsCommandBinding() { } - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } - } - public class PanelSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger - { - public PanelSetMethodBindingConverter() { } - public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } - public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } - } - public class PlatformOperations : ReactiveUI.IPlatformOperations - { - public PlatformOperations() { } - public string? GetOrientation() { } - } - public class ReactiveUserControlNonGeneric : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor - { - public ReactiveUserControlNonGeneric() { } - protected override void Dispose(bool disposing) { } - } - public class ReactiveUserControl : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public ReactiveUserControl() { } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The ViewModel.")] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)] - public TViewModel ViewModel { get; set; } - protected override void Dispose(bool disposing) { } - } - public class Registrations - { - public Registrations() { } - public void Register(System.Action, System.Type> registerFunction) { } - } - [System.ComponentModel.DefaultProperty("ViewModel")] - public class RoutedControlHost : System.Windows.Forms.UserControl, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging - { - public RoutedControlHost() { } - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The default control when no viewmodel is specified")] - public System.Windows.Forms.Control? DefaultContent { get; set; } - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The router.")] - public ReactiveUI.RoutingState? Router { get; set; } - [System.ComponentModel.Browsable(false)] - public System.IObservable? ViewContractObservable { get; set; } - [System.ComponentModel.Browsable(false)] - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; - public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; - protected override void Dispose(bool disposing) { } - } - public class TableContentSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger - { - public TableContentSetMethodBindingConverter() { } - public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } - public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } - } - [System.ComponentModel.DefaultProperty("ViewModel")] - public class ViewModelControlHost : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IReactiveObject, ReactiveUI.IViewFor, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging - { - public ViewModelControlHost() { } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.DefaultValue(true)] - [System.ComponentModel.Description("Cache Views")] - public bool CacheViews { get; set; } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The Current View")] - public object? Content { get; set; } - public System.Windows.Forms.Control? CurrentView { get; } - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The default control when no viewmodel is specified")] - public System.Windows.Forms.Control? DefaultContent { get; set; } - [System.ComponentModel.Browsable(false)] - public System.IObservable? ViewContractObservable { get; set; } - [System.ComponentModel.Browsable(false)] - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The viewmodel to host.")] - public object? ViewModel { get; set; } - public static bool DefaultCacheViewsEnabled { get; set; } - public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; - public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; - protected override void Dispose(bool disposing) { } - } - public class WinformsCreatesObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger - { - public WinformsCreatesObservableForProperty() { } - public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } - } -} \ No newline at end of file diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet7_0.verified.txt b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet7_0.verified.txt deleted file mode 100644 index f8bac7dbb5..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet7_0.verified.txt +++ /dev/null @@ -1,116 +0,0 @@ -[assembly: System.Runtime.Versioning.SupportedOSPlatform("Windows10.0.17763.0")] -[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v7.0", FrameworkDisplayName=".NET 7.0")] -[assembly: System.Runtime.Versioning.TargetPlatform("Windows10.0.17763.0")] -namespace ReactiveUI.Winforms -{ - public class ActivationForViewFetcher : ReactiveUI.IActivationForViewFetcher, Splat.IEnableLogger - { - public ActivationForViewFetcher() { } - public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } - public int GetAffinityForView(System.Type view) { } - } - public class ContentControlBindingHook : ReactiveUI.IPropertyBindingHook - { - public ContentControlBindingHook() { } - public bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction) { } - } - public class CreatesWinformsCommandBinding : ReactiveUI.ICreatesCommandBinding - { - public CreatesWinformsCommandBinding() { } - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } - } - public class PanelSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger - { - public PanelSetMethodBindingConverter() { } - public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } - public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } - } - public class PlatformOperations : ReactiveUI.IPlatformOperations - { - public PlatformOperations() { } - public string? GetOrientation() { } - } - public class ReactiveUserControlNonGeneric : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor - { - public ReactiveUserControlNonGeneric() { } - protected override void Dispose(bool disposing) { } - } - public class ReactiveUserControl : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public ReactiveUserControl() { } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The ViewModel.")] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)] - public TViewModel ViewModel { get; set; } - protected override void Dispose(bool disposing) { } - } - public class Registrations - { - public Registrations() { } - public void Register(System.Action, System.Type> registerFunction) { } - } - [System.ComponentModel.DefaultProperty("ViewModel")] - public class RoutedControlHost : System.Windows.Forms.UserControl, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging - { - public RoutedControlHost() { } - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The default control when no viewmodel is specified")] - public System.Windows.Forms.Control? DefaultContent { get; set; } - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The router.")] - public ReactiveUI.RoutingState? Router { get; set; } - [System.ComponentModel.Browsable(false)] - public System.IObservable? ViewContractObservable { get; set; } - [System.ComponentModel.Browsable(false)] - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; - public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; - protected override void Dispose(bool disposing) { } - } - public class TableContentSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger - { - public TableContentSetMethodBindingConverter() { } - public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } - public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } - } - [System.ComponentModel.DefaultProperty("ViewModel")] - public class ViewModelControlHost : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IReactiveObject, ReactiveUI.IViewFor, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging - { - public ViewModelControlHost() { } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.DefaultValue(true)] - [System.ComponentModel.Description("Cache Views")] - public bool CacheViews { get; set; } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The Current View")] - public object? Content { get; set; } - public System.Windows.Forms.Control? CurrentView { get; } - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The default control when no viewmodel is specified")] - public System.Windows.Forms.Control? DefaultContent { get; set; } - [System.ComponentModel.Browsable(false)] - public System.IObservable? ViewContractObservable { get; set; } - [System.ComponentModel.Browsable(false)] - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The viewmodel to host.")] - public object? ViewModel { get; set; } - public static bool DefaultCacheViewsEnabled { get; set; } - public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; - public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; - protected override void Dispose(bool disposing) { } - } - public class WinformsCreatesObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger - { - public WinformsCreatesObservableForProperty() { } - public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } - } -} \ No newline at end of file diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet8_0.verified.txt b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet8_0.verified.txt deleted file mode 100644 index 0254b09eb6..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet8_0.verified.txt +++ /dev/null @@ -1,170 +0,0 @@ -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] -namespace ReactiveUI.Builder -{ - public static class WinFormsReactiveUIBuilderExtensions - { - public static System.Reactive.Concurrency.IScheduler WinFormsMainThreadScheduler { get; } - public static ReactiveUI.Builder.IReactiveUIBuilder WithWinForms(this ReactiveUI.Builder.IReactiveUIBuilder builder) { } - public static ReactiveUI.Builder.IReactiveUIBuilder WithWinFormsScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder) { } - } -} -namespace ReactiveUI.Winforms -{ - public class ActivationForViewFetcher : ReactiveUI.IActivationForViewFetcher, Splat.IEnableLogger - { - public ActivationForViewFetcher() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] - public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } - public int GetAffinityForView(System.Type view) { } - } - public class ContentControlBindingHook : ReactiveUI.IPropertyBindingHook - { - public ContentControlBindingHook() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] - public bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction) { } - } - public class CreatesWinformsCommandBinding : ReactiveUI.ICreatesCommandBinding - { - public CreatesWinformsCommandBinding() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } - public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } - } - public class PanelSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger - { - public PanelSetMethodBindingConverter() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] - public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } - public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } - } - public class PlatformOperations : ReactiveUI.IPlatformOperations - { - public PlatformOperations() { } - public string? GetOrientation() { } - } - public class ReactiveUserControlNonGeneric : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor - { - public ReactiveUserControlNonGeneric() { } - protected override void Dispose(bool disposing) { } - } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveUserControl provides base functionality for ReactiveUI which " + - "may require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveUserControl provides base functionality for ReactiveUI which " + - "may require unreferenced code")] - public class ReactiveUserControl : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public ReactiveUserControl() { } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The ViewModel.")] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)] - public TViewModel ViewModel { get; set; } - protected override void Dispose(bool disposing) { } - } - public class Registrations : ReactiveUI.IWantsToRegisterStuff - { - public Registrations() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - public void Register(System.Action, System.Type> registerFunction) { } - } - [System.ComponentModel.DefaultProperty("ViewModel")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutedControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutedControlHost uses methods that may require unreferenced code")] - public class RoutedControlHost : System.Windows.Forms.UserControl, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging - { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutedControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutedControlHost uses methods that may require unreferenced code")] - public RoutedControlHost() { } - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The default control when no viewmodel is specified")] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public System.Windows.Forms.Control? DefaultContent { get; set; } - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The router.")] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public ReactiveUI.RoutingState? Router { get; set; } - [System.ComponentModel.Browsable(false)] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public System.IObservable? ViewContractObservable { get; set; } - [System.ComponentModel.Browsable(false)] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; - public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; - protected override void Dispose(bool disposing) { } - } - public class TableContentSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger - { - public TableContentSetMethodBindingConverter() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] - public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } - public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } - } - [System.ComponentModel.DefaultProperty("ViewModel")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewModelControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ViewModelControlHost uses methods that may require unreferenced code")] - public class ViewModelControlHost : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IReactiveObject, ReactiveUI.IViewFor, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging - { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewModelControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ViewModelControlHost uses methods that may require unreferenced code")] - public ViewModelControlHost() { } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.DefaultValue(true)] - [System.ComponentModel.Description("Cache Views")] - public bool CacheViews { get; set; } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The Current View")] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public object? Content { get; protected set; } - public System.Windows.Forms.Control? CurrentView { get; } - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The default control when no viewmodel is specified")] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public System.Windows.Forms.Control? DefaultContent { get; set; } - [System.ComponentModel.Browsable(false)] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public System.IObservable? ViewContractObservable { get; set; } - [System.ComponentModel.Browsable(false)] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - [System.ComponentModel.Bindable(true)] - [System.ComponentModel.Category("ReactiveUI")] - [System.ComponentModel.Description("The viewmodel to host.")] - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public object? ViewModel { get; set; } - [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)] - public static bool DefaultCacheViewsEnabled { get; set; } - public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; - public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; - protected override void Dispose(bool disposing) { } - } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WinformsCreatesObservableForProperty uses methods that require dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WinformsCreatesObservableForProperty uses methods that may require unreferenced c" + - "ode")] - public class WinformsCreatesObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger - { - public WinformsCreatesObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] - public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } - } -} \ No newline at end of file diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/Mocks/FakeViewLocator.cs b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/Mocks/FakeViewLocator.cs deleted file mode 100644 index 101b6e2983..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/Mocks/FakeViewLocator.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2025 .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.Winforms; - -internal class FakeViewLocator : IViewLocator -{ - public Func? LocatorFunc { get; set; } - - public IViewFor? ResolveView(T? viewModel, string? contract = null) - { - if (viewModel is null) - { - throw new ArgumentNullException(nameof(viewModel)); - } - - return LocatorFunc?.Invoke(viewModel.GetType()); - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/Mocks/TestForm.Designer.cs b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/Mocks/TestForm.Designer.cs deleted file mode 100644 index 19642cdb52..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/Mocks/TestForm.Designer.cs +++ /dev/null @@ -1,62 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace ReactiveUI.Tests.Platforms.winforms.Mocks; -using System; - - -/// -/// A strongly-typed resource class, for looking up localized strings, etc. -/// -// This class was auto-generated by the StronglyTypedResourceBuilder -// class via a tool like ResGen or Visual Studio. -// To add or remove a member, edit your .ResX file then rerun ResGen -// with the /str option, or rebuild your VS project. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] -[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] -[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] -internal class TestForm { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal TestForm() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ReactiveUI.Tests.Platforms.Winforms.Mocks.TestForm", typeof(TestForm).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/Mocks/TestFormNotCanActivate.Designer.cs b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/Mocks/TestFormNotCanActivate.Designer.cs deleted file mode 100644 index 98ae0a5f09..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/Mocks/TestFormNotCanActivate.Designer.cs +++ /dev/null @@ -1,62 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace ReactiveUI.Tests.Platforms.winforms.Mocks; -using System; - - -/// -/// A strongly-typed resource class, for looking up localized strings, etc. -/// -// This class was auto-generated by the StronglyTypedResourceBuilder -// class via a tool like ResGen or Visual Studio. -// To add or remove a member, edit your .ResX file then rerun ResGen -// with the /str option, or rebuild your VS project. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] -[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] -[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] -internal class TestFormNotCanActivate { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal TestFormNotCanActivate() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ReactiveUI.Tests.Platforms.Winforms.Mocks.TestFormNotCanActivate", typeof(TestFormNotCanActivate).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet10_0.verified.txt b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet10_0.verified.txt deleted file mode 100644 index dddae7b36c..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet10_0.verified.txt +++ /dev/null @@ -1,176 +0,0 @@ -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] -[assembly: System.Windows.Markup.XmlnsDefinition("http://reactiveui.net", "ReactiveUI")] -[assembly: System.Windows.ThemeInfo(System.Windows.ResourceDictionaryLocation.None, System.Windows.ResourceDictionaryLocation.SourceAssembly)] -namespace ReactiveUI -{ - public class ActivationForViewFetcher : ReactiveUI.IActivationForViewFetcher - { - public ActivationForViewFetcher() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] - public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } - public int GetAffinityForView(System.Type view) { } - } - public class AutoDataTemplateBindingHook : ReactiveUI.IPropertyBindingHook - { - public AutoDataTemplateBindingHook() { } - public static System.Lazy DefaultItemTemplate { get; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] - public bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction) { } - } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AutoSuspendHelper uses RxApp.SuspensionHost and TaskpoolScheduler which require d" + - "ynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AutoSuspendHelper uses RxApp.SuspensionHost and TaskpoolScheduler which may requi" + - "re unreferenced code")] - public class AutoSuspendHelper : Splat.IEnableLogger - { - public AutoSuspendHelper(System.Windows.Application app) { } - public System.TimeSpan IdleTimeout { get; set; } - } - [System.Flags] - public enum BooleanToVisibilityHint - { - None = 0, - Inverse = 2, - UseHidden = 4, - } - public class BooleanToVisibilityTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger - { - public BooleanToVisibilityTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } - } - public class DependencyObjectObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger - { - public DependencyObjectObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] - public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } - } - public class PlatformOperations : ReactiveUI.IPlatformOperations - { - public PlatformOperations() { } - public string? GetOrientation() { } - } - public class ReactivePage : System.Windows.Controls.Page, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ReactivePage() { } - public TViewModel BindingRoot { get; } - public TViewModel ViewModel { get; set; } - } - public class ReactiveUserControl : System.Windows.Controls.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ReactiveUserControl() { } - public TViewModel BindingRoot { get; } - public TViewModel ViewModel { get; set; } - } - public class ReactiveWindow : System.Windows.Window, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ReactiveWindow() { } - public TViewModel BindingRoot { get; } - public TViewModel ViewModel { get; set; } - } - public class RoutedViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, Splat.IEnableLogger - { - public static readonly System.Windows.DependencyProperty DefaultContentProperty; - public static readonly System.Windows.DependencyProperty RouterProperty; - public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] - public RoutedViewHost() { } - public object DefaultContent { get; set; } - public ReactiveUI.RoutingState Router { get; set; } - public string? ViewContract { get; set; } - public System.IObservable ViewContractObservable { get; set; } - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - } - [System.Windows.TemplatePart(Name="PART_Container", Type=typeof(System.Windows.FrameworkElement?))] - [System.Windows.TemplatePart(Name="PART_CurrentContentPresentationSite", Type=typeof(System.Windows.Controls.ContentPresenter?))] - [System.Windows.TemplatePart(Name="PART_PreviousImageSite", Type=typeof(System.Windows.Controls.Image?))] - [System.Windows.TemplateVisualState(GroupName="PresentationStates", Name="Normal")] - public class TransitioningContentControl : System.Windows.Controls.ContentControl - { - public static readonly System.Windows.DependencyProperty TransitionDirectionProperty; - public static readonly System.Windows.DependencyProperty TransitionDurationProperty; - public static readonly System.Windows.DependencyProperty TransitionProperty; - public TransitioningContentControl() { } - public ReactiveUI.TransitioningContentControl.TransitionDirection Direction { get; set; } - public System.TimeSpan Duration { get; set; } - public ReactiveUI.TransitioningContentControl.TransitionType Transition { get; set; } - public event System.Windows.RoutedEventHandler? TransitionCompleted; - public event System.Windows.RoutedEventHandler? TransitionStarted; - public override void OnApplyTemplate() { } - protected override void OnContentChanged(object oldContent, object newContent) { } - public enum TransitionDirection - { - Up = 0, - Down = 1, - Left = 2, - Right = 3, - } - public enum TransitionType - { - Fade = 0, - Move = 1, - Slide = 2, - Drop = 3, - Bounce = 4, - } - } - public static class ValidationBindingMixins - { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindWithValidation uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindWithValidation uses methods that may require unreferenced code")] - public static ReactiveUI.IReactiveBinding BindWithValidation(this TView view, TViewModel viewModel, System.Linq.Expressions.Expression> viewModelPropertySelector, System.Linq.Expressions.Expression> frameworkElementSelector) - where TViewModel : class - where TView : class, ReactiveUI.IViewFor { } - } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp which require dynam" + - "ic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp which may require u" + - "nreferenced code")] - public class ViewModelViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, Splat.IEnableLogger - { - public static readonly System.Windows.DependencyProperty ContractFallbackByPassProperty; - public static readonly System.Windows.DependencyProperty DefaultContentProperty; - public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ViewModelViewHost() { } - public bool ContractFallbackByPass { get; set; } - public object DefaultContent { get; set; } - public string? ViewContract { get; set; } - public System.IObservable ViewContractObservable { get; set; } - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - public object? ViewModel { get; set; } - protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { } - } -} -namespace ReactiveUI.Builder -{ - public static class WpfReactiveUIBuilderExtensions - { - public static System.Reactive.Concurrency.IScheduler WpfMainThreadScheduler { get; } - public static ReactiveUI.Builder.IReactiveUIBuilder WithWpf(this ReactiveUI.Builder.IReactiveUIBuilder builder) { } - public static ReactiveUI.Builder.IReactiveUIBuilder WithWpfScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder) { } - } -} -namespace ReactiveUI.Wpf -{ - public class Registrations : ReactiveUI.IWantsToRegisterStuff - { - public Registrations() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - public void Register(System.Action, System.Type> registerFunction) { } - } -} \ No newline at end of file diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet8_0.verified.txt b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet8_0.verified.txt deleted file mode 100644 index dddae7b36c..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet8_0.verified.txt +++ /dev/null @@ -1,176 +0,0 @@ -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] -[assembly: System.Windows.Markup.XmlnsDefinition("http://reactiveui.net", "ReactiveUI")] -[assembly: System.Windows.ThemeInfo(System.Windows.ResourceDictionaryLocation.None, System.Windows.ResourceDictionaryLocation.SourceAssembly)] -namespace ReactiveUI -{ - public class ActivationForViewFetcher : ReactiveUI.IActivationForViewFetcher - { - public ActivationForViewFetcher() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] - public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } - public int GetAffinityForView(System.Type view) { } - } - public class AutoDataTemplateBindingHook : ReactiveUI.IPropertyBindingHook - { - public AutoDataTemplateBindingHook() { } - public static System.Lazy DefaultItemTemplate { get; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] - public bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction) { } - } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AutoSuspendHelper uses RxApp.SuspensionHost and TaskpoolScheduler which require d" + - "ynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AutoSuspendHelper uses RxApp.SuspensionHost and TaskpoolScheduler which may requi" + - "re unreferenced code")] - public class AutoSuspendHelper : Splat.IEnableLogger - { - public AutoSuspendHelper(System.Windows.Application app) { } - public System.TimeSpan IdleTimeout { get; set; } - } - [System.Flags] - public enum BooleanToVisibilityHint - { - None = 0, - Inverse = 2, - UseHidden = 4, - } - public class BooleanToVisibilityTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger - { - public BooleanToVisibilityTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } - } - public class DependencyObjectObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger - { - public DependencyObjectObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] - public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } - } - public class PlatformOperations : ReactiveUI.IPlatformOperations - { - public PlatformOperations() { } - public string? GetOrientation() { } - } - public class ReactivePage : System.Windows.Controls.Page, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ReactivePage() { } - public TViewModel BindingRoot { get; } - public TViewModel ViewModel { get; set; } - } - public class ReactiveUserControl : System.Windows.Controls.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ReactiveUserControl() { } - public TViewModel BindingRoot { get; } - public TViewModel ViewModel { get; set; } - } - public class ReactiveWindow : System.Windows.Window, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ReactiveWindow() { } - public TViewModel BindingRoot { get; } - public TViewModel ViewModel { get; set; } - } - public class RoutedViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, Splat.IEnableLogger - { - public static readonly System.Windows.DependencyProperty DefaultContentProperty; - public static readonly System.Windows.DependencyProperty RouterProperty; - public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] - public RoutedViewHost() { } - public object DefaultContent { get; set; } - public ReactiveUI.RoutingState Router { get; set; } - public string? ViewContract { get; set; } - public System.IObservable ViewContractObservable { get; set; } - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - } - [System.Windows.TemplatePart(Name="PART_Container", Type=typeof(System.Windows.FrameworkElement?))] - [System.Windows.TemplatePart(Name="PART_CurrentContentPresentationSite", Type=typeof(System.Windows.Controls.ContentPresenter?))] - [System.Windows.TemplatePart(Name="PART_PreviousImageSite", Type=typeof(System.Windows.Controls.Image?))] - [System.Windows.TemplateVisualState(GroupName="PresentationStates", Name="Normal")] - public class TransitioningContentControl : System.Windows.Controls.ContentControl - { - public static readonly System.Windows.DependencyProperty TransitionDirectionProperty; - public static readonly System.Windows.DependencyProperty TransitionDurationProperty; - public static readonly System.Windows.DependencyProperty TransitionProperty; - public TransitioningContentControl() { } - public ReactiveUI.TransitioningContentControl.TransitionDirection Direction { get; set; } - public System.TimeSpan Duration { get; set; } - public ReactiveUI.TransitioningContentControl.TransitionType Transition { get; set; } - public event System.Windows.RoutedEventHandler? TransitionCompleted; - public event System.Windows.RoutedEventHandler? TransitionStarted; - public override void OnApplyTemplate() { } - protected override void OnContentChanged(object oldContent, object newContent) { } - public enum TransitionDirection - { - Up = 0, - Down = 1, - Left = 2, - Right = 3, - } - public enum TransitionType - { - Fade = 0, - Move = 1, - Slide = 2, - Drop = 3, - Bounce = 4, - } - } - public static class ValidationBindingMixins - { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindWithValidation uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindWithValidation uses methods that may require unreferenced code")] - public static ReactiveUI.IReactiveBinding BindWithValidation(this TView view, TViewModel viewModel, System.Linq.Expressions.Expression> viewModelPropertySelector, System.Linq.Expressions.Expression> frameworkElementSelector) - where TViewModel : class - where TView : class, ReactiveUI.IViewFor { } - } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp which require dynam" + - "ic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp which may require u" + - "nreferenced code")] - public class ViewModelViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, Splat.IEnableLogger - { - public static readonly System.Windows.DependencyProperty ContractFallbackByPassProperty; - public static readonly System.Windows.DependencyProperty DefaultContentProperty; - public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ViewModelViewHost() { } - public bool ContractFallbackByPass { get; set; } - public object DefaultContent { get; set; } - public string? ViewContract { get; set; } - public System.IObservable ViewContractObservable { get; set; } - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - public object? ViewModel { get; set; } - protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { } - } -} -namespace ReactiveUI.Builder -{ - public static class WpfReactiveUIBuilderExtensions - { - public static System.Reactive.Concurrency.IScheduler WpfMainThreadScheduler { get; } - public static ReactiveUI.Builder.IReactiveUIBuilder WithWpf(this ReactiveUI.Builder.IReactiveUIBuilder builder) { } - public static ReactiveUI.Builder.IReactiveUIBuilder WithWpfScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder) { } - } -} -namespace ReactiveUI.Wpf -{ - public class Registrations : ReactiveUI.IWantsToRegisterStuff - { - public Registrations() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - public void Register(System.Action, System.Type> registerFunction) { } - } -} \ No newline at end of file diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet9_0.verified.txt b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet9_0.verified.txt deleted file mode 100644 index dddae7b36c..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.Wpf.DotNet9_0.verified.txt +++ /dev/null @@ -1,176 +0,0 @@ -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] -[assembly: System.Windows.Markup.XmlnsDefinition("http://reactiveui.net", "ReactiveUI")] -[assembly: System.Windows.ThemeInfo(System.Windows.ResourceDictionaryLocation.None, System.Windows.ResourceDictionaryLocation.SourceAssembly)] -namespace ReactiveUI -{ - public class ActivationForViewFetcher : ReactiveUI.IActivationForViewFetcher - { - public ActivationForViewFetcher() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] - public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } - public int GetAffinityForView(System.Type view) { } - } - public class AutoDataTemplateBindingHook : ReactiveUI.IPropertyBindingHook - { - public AutoDataTemplateBindingHook() { } - public static System.Lazy DefaultItemTemplate { get; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] - public bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction) { } - } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AutoSuspendHelper uses RxApp.SuspensionHost and TaskpoolScheduler which require d" + - "ynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AutoSuspendHelper uses RxApp.SuspensionHost and TaskpoolScheduler which may requi" + - "re unreferenced code")] - public class AutoSuspendHelper : Splat.IEnableLogger - { - public AutoSuspendHelper(System.Windows.Application app) { } - public System.TimeSpan IdleTimeout { get; set; } - } - [System.Flags] - public enum BooleanToVisibilityHint - { - None = 0, - Inverse = 2, - UseHidden = 4, - } - public class BooleanToVisibilityTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger - { - public BooleanToVisibilityTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } - } - public class DependencyObjectObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger - { - public DependencyObjectObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] - public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } - } - public class PlatformOperations : ReactiveUI.IPlatformOperations - { - public PlatformOperations() { } - public string? GetOrientation() { } - } - public class ReactivePage : System.Windows.Controls.Page, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ReactivePage() { } - public TViewModel BindingRoot { get; } - public TViewModel ViewModel { get; set; } - } - public class ReactiveUserControl : System.Windows.Controls.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ReactiveUserControl() { } - public TViewModel BindingRoot { get; } - public TViewModel ViewModel { get; set; } - } - public class ReactiveWindow : System.Windows.Window, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor - where TViewModel : class - { - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ReactiveWindow() { } - public TViewModel BindingRoot { get; } - public TViewModel ViewModel { get; set; } - } - public class RoutedViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, Splat.IEnableLogger - { - public static readonly System.Windows.DependencyProperty DefaultContentProperty; - public static readonly System.Windows.DependencyProperty RouterProperty; - public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutedViewHost uses methods that may require unreferenced code")] - public RoutedViewHost() { } - public object DefaultContent { get; set; } - public ReactiveUI.RoutingState Router { get; set; } - public string? ViewContract { get; set; } - public System.IObservable ViewContractObservable { get; set; } - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - } - [System.Windows.TemplatePart(Name="PART_Container", Type=typeof(System.Windows.FrameworkElement?))] - [System.Windows.TemplatePart(Name="PART_CurrentContentPresentationSite", Type=typeof(System.Windows.Controls.ContentPresenter?))] - [System.Windows.TemplatePart(Name="PART_PreviousImageSite", Type=typeof(System.Windows.Controls.Image?))] - [System.Windows.TemplateVisualState(GroupName="PresentationStates", Name="Normal")] - public class TransitioningContentControl : System.Windows.Controls.ContentControl - { - public static readonly System.Windows.DependencyProperty TransitionDirectionProperty; - public static readonly System.Windows.DependencyProperty TransitionDurationProperty; - public static readonly System.Windows.DependencyProperty TransitionProperty; - public TransitioningContentControl() { } - public ReactiveUI.TransitioningContentControl.TransitionDirection Direction { get; set; } - public System.TimeSpan Duration { get; set; } - public ReactiveUI.TransitioningContentControl.TransitionType Transition { get; set; } - public event System.Windows.RoutedEventHandler? TransitionCompleted; - public event System.Windows.RoutedEventHandler? TransitionStarted; - public override void OnApplyTemplate() { } - protected override void OnContentChanged(object oldContent, object newContent) { } - public enum TransitionDirection - { - Up = 0, - Down = 1, - Left = 2, - Right = 3, - } - public enum TransitionType - { - Fade = 0, - Move = 1, - Slide = 2, - Drop = 3, - Bounce = 4, - } - } - public static class ValidationBindingMixins - { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindWithValidation uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindWithValidation uses methods that may require unreferenced code")] - public static ReactiveUI.IReactiveBinding BindWithValidation(this TView view, TViewModel viewModel, System.Linq.Expressions.Expression> viewModelPropertySelector, System.Linq.Expressions.Expression> frameworkElementSelector) - where TViewModel : class - where TView : class, ReactiveUI.IViewFor { } - } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp which require dynam" + - "ic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp which may require u" + - "nreferenced code")] - public class ViewModelViewHost : ReactiveUI.TransitioningContentControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, Splat.IEnableLogger - { - public static readonly System.Windows.DependencyProperty ContractFallbackByPassProperty; - public static readonly System.Windows.DependencyProperty DefaultContentProperty; - public static readonly System.Windows.DependencyProperty ViewContractObservableProperty; - public static readonly System.Windows.DependencyProperty ViewModelProperty; - public ViewModelViewHost() { } - public bool ContractFallbackByPass { get; set; } - public object DefaultContent { get; set; } - public string? ViewContract { get; set; } - public System.IObservable ViewContractObservable { get; set; } - public ReactiveUI.IViewLocator? ViewLocator { get; set; } - public object? ViewModel { get; set; } - protected virtual void ResolveViewForViewModel(object? viewModel, string? contract) { } - } -} -namespace ReactiveUI.Builder -{ - public static class WpfReactiveUIBuilderExtensions - { - public static System.Reactive.Concurrency.IScheduler WpfMainThreadScheduler { get; } - public static ReactiveUI.Builder.IReactiveUIBuilder WithWpf(this ReactiveUI.Builder.IReactiveUIBuilder builder) { } - public static ReactiveUI.Builder.IReactiveUIBuilder WithWpfScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder) { } - } -} -namespace ReactiveUI.Wpf -{ - public class Registrations : ReactiveUI.IWantsToRegisterStuff - { - public Registrations() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - public void Register(System.Action, System.Type> registerFunction) { } - } -} \ No newline at end of file diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.cs deleted file mode 100644 index a87577a53f..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/API/WpfApiApprovalTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2025 .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.Wpf; - -/// -/// Checks the WPF API to make sure there aren't any unexpected public API changes. -/// -[ExcludeFromCodeCoverage] -public class WpfApiApprovalTests -{ - /// - /// Checks the approved vs the received API. - /// - /// A task to monitor the process. - [Test] - public Task Wpf() => typeof(ReactiveWindow<>).Assembly.CheckApproval(["ReactiveUI"]); -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/BooleanToVisibilityTypeConverterTest.cs b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/BooleanToVisibilityTypeConverterTest.cs deleted file mode 100644 index 9e9d212a8b..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/BooleanToVisibilityTypeConverterTest.cs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2025 .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; - -namespace ReactiveUI.Tests.Wpf; - -/// -/// Tests for in WPF. -/// -public class BooleanToVisibilityTypeConverterTest -{ - /// - /// Tests that GetAffinityForObjects returns correct affinity for bool to Visibility. - /// - /// A representing the asynchronous operation. - [Test] - public async Task GetAffinityForObjects_ReturnsCorrectAffinityForBoolToVisibility() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var affinity = converter.GetAffinityForObjects(typeof(bool), typeof(Visibility)); - - await Assert.That(affinity).IsEqualTo(10); - } - - /// - /// Tests that GetAffinityForObjects returns correct affinity for Visibility to bool. - /// - /// A representing the asynchronous operation. - [Test] - public async Task GetAffinityForObjects_ReturnsCorrectAffinityForVisibilityToBool() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var affinity = converter.GetAffinityForObjects(typeof(Visibility), typeof(bool)); - - await Assert.That(affinity).IsEqualTo(10); - } - - /// - /// Tests that GetAffinityForObjects returns zero for unsupported types. - /// - /// A representing the asynchronous operation. - [Test] - public async Task GetAffinityForObjects_ReturnsZeroForUnsupportedTypes() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var affinity = converter.GetAffinityForObjects(typeof(int), typeof(string)); - - await Assert.That(affinity).IsEqualTo(0); - } - - /// - /// Tests that TryConvert converts true to Visibility.Visible. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_ConvertsTrueToVisible() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert(true, typeof(Visibility), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(Visibility.Visible); - } - - /// - /// Tests that TryConvert converts false to Visibility.Collapsed (default). - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_ConvertsFalseToCollapsed() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert(false, typeof(Visibility), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(Visibility.Collapsed); - } - - /// - /// Tests that TryConvert with Inverse hint inverts the conversion. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_WithInverseHint_InvertsConversion() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert(true, typeof(Visibility), BooleanToVisibilityHint.Inverse, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(Visibility.Collapsed); - } - - /// - /// Tests that TryConvert with UseHidden hint uses Hidden instead of Collapsed (WPF-specific). - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_WithUseHiddenHint_UsesHidden() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert(false, typeof(Visibility), BooleanToVisibilityHint.UseHidden, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(Visibility.Hidden); - } - - /// - /// Tests that TryConvert with both Inverse and UseHidden hints works correctly (WPF-specific). - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_WithInverseAndUseHidden_UsesHidden() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert( - true, - typeof(Visibility), - BooleanToVisibilityHint.Inverse | BooleanToVisibilityHint.UseHidden, - out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(Visibility.Hidden); - } - - /// - /// Tests that TryConvert converts Visibility.Visible to false (XOR logic). - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_ConvertsVisibleToFalse() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert(Visibility.Visible, typeof(bool), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(false); - } - - /// - /// Tests that TryConvert converts Visibility.Collapsed to true (XOR logic). - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_ConvertsCollapsedToTrue() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert(Visibility.Collapsed, typeof(bool), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(true); - } - - /// - /// Tests that TryConvert converts Visibility.Hidden to true (WPF-specific, XOR logic). - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_ConvertsHiddenToTrue() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert(Visibility.Hidden, typeof(bool), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(true); - } - - /// - /// Tests that TryConvert with Inverse hint on Visibility to bool inverts the result. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_VisibilityToBoolWithInverse_InvertsResult() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var successVisible = converter.TryConvert(Visibility.Visible, typeof(bool), BooleanToVisibilityHint.Inverse, out var resultVisible); - var successCollapsed = converter.TryConvert(Visibility.Collapsed, typeof(bool), BooleanToVisibilityHint.Inverse, out var resultCollapsed); - - await Assert.That(successVisible).IsTrue(); - await Assert.That(successCollapsed).IsTrue(); - await Assert.That(resultVisible).IsEqualTo(true); - await Assert.That(resultCollapsed).IsEqualTo(false); - } - - /// - /// Tests that TryConvert with non-Visibility, non-bool input defaults to Visible. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_NonVisibilityInput_DefaultsToVisible() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert("some string", typeof(bool), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(Visibility.Visible); - } - - /// - /// Tests that TryConvert with null input defaults to Visible. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_NullInput_DefaultsToVisible() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert(null, typeof(bool), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(Visibility.Visible); - } - - /// - /// Tests that TryConvert with UseHidden hint on true stays Visible. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_TrueWithUseHidden_StaysVisible() - { - var converter = new BooleanToVisibilityTypeConverter(); - - var success = converter.TryConvert(true, typeof(Visibility), BooleanToVisibilityHint.UseHidden, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(Visibility.Visible); - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/DefaultViewLocatorTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/DefaultViewLocatorTests.cs deleted file mode 100644 index e8796f9ee0..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/DefaultViewLocatorTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2025 .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.Core; - -namespace ReactiveUI.Tests; - -/// -/// Contains unit tests for the class, verifying view resolution behavior in WPF scenarios. -/// -[NotInParallel] -public partial class DefaultViewLocatorTests -{ - /// - /// Tests that whether this instance [can resolve view from view model with IRoutableViewModel]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanResolveViewFromViewModelWithIRoutableViewModelType() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - // Register for both the interface and the concrete type - resolver.Register(static () => new RoutableFooView(), typeof(IViewFor)); - resolver.Register(static () => new RoutableFooView(), typeof(IViewFor)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator(); - var vm = new RoutableFooViewModel(); - - var result = fixture.ResolveView(vm); - - await Assert.That(result).IsTypeOf(); - } - } - - /// - /// Tests that make sure this instance [can override name resolution function]. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CanOverrideNameResolutionFunc() - { - var resolver = new ModernDependencyResolver(); - - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - resolver.Register(static () => new RoutableFooCustomView(), typeof(IViewFor)); - resolver.Register(static () => new RoutableFooCustomView(), typeof(IViewFor)); - - using (resolver.WithResolver()) - { - var fixture = new DefaultViewLocator - { - ViewModelToViewFunc = static x => x.Replace("ViewModel", "CustomView") - }; - var vm = new RoutableFooViewModel(); - - var result = fixture.ResolveView(vm); - await Assert.That(result).IsTypeOf(); - } - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/Mocks/FakeXamlCommandBindingView.cs b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/Mocks/FakeXamlCommandBindingView.cs deleted file mode 100644 index 0ed4ab7054..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/Mocks/FakeXamlCommandBindingView.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2025 .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.Controls; - -namespace ReactiveUI.Tests.Wpf; - -/// -/// A fake xaml command binding view. -/// -public class FakeXamlCommandBindingView : IViewFor -{ - private readonly Button _buttonDeclaredInXaml; - - /// - /// Initializes a new instance of the class. - /// - public FakeXamlCommandBindingView() - { - _buttonDeclaredInXaml = new Button(); - - this.BindCommand(ViewModel, static vm => vm!.Command2!, static v => v._buttonDeclaredInXaml); - } - - /// - /// Gets the name of button declared in xaml. - /// - public string NameOfButtonDeclaredInXaml => nameof(_buttonDeclaredInXaml); - - /// - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (CommandBindingViewModel?)value; - } - - /// - public CommandBindingViewModel? ViewModel { get; set; } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/WpfActiveContentTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/WpfActiveContentTests.cs deleted file mode 100644 index d16ba2b39f..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/wpf/WpfActiveContentTests.cs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2025 .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.Reactive.Disposables.Fluent; -using System.Windows; -using DynamicData; -using ReactiveUI.Testing; - -using TUnit.Core.Executors; - -namespace ReactiveUI.Tests.Wpf; - -/// -/// Wpf Active Content Tests. -/// -[NotInParallel] -public class WpfActiveContentTests -{ - - /// - /// Validates binding logic for a list-backed view. - /// - /// A representing the asynchronous operation. - [Test] - [TestExecutor] - public async Task BindListFunctionalTest() - { - var view = new MockBindListView(); - var vm = view.ViewModel!; - - // Activate the view to trigger bindings - view.RaiseEvent(new RoutedEventArgs(FrameworkElement.LoadedEvent)); - - // Test 1: Add first item - var test1 = new MockBindListItemViewModel("Test1"); - vm.ActiveListItem.Add(test1); - using (Assert.Multiple()) - { - await Assert.That(vm.ListItems.Count).IsEqualTo(1); - await Assert.That(vm.ActiveItem).IsEqualTo(test1); - await Assert.That(view.ItemList.Items.Count).IsEqualTo(1); - } - - // Test 2: Add second item - var test2 = new MockBindListItemViewModel("Test2"); - vm.ActiveListItem.Add(test2); - using (Assert.Multiple()) - { - await Assert.That(vm.ListItems.Count).IsEqualTo(2); - await Assert.That(vm.ActiveItem).IsEqualTo(test2); - await Assert.That(view.ItemList.Items.Count).IsEqualTo(2); - } - - // Test 3: Add third item - var test3 = new MockBindListItemViewModel("Test3"); - vm.ActiveListItem.Add(test3); - using (Assert.Multiple()) - { - await Assert.That(vm.ListItems.Count).IsEqualTo(3); - await Assert.That(vm.ActiveItem).IsEqualTo(test3); - await Assert.That(view.ItemList.Items.Count).IsEqualTo(3); - } - - // Test 4: Select first item (should trigger command that removes items after it) - await vm.SelectItem.Execute(test1); - using (Assert.Multiple()) - { - await Assert.That(vm.ListItems.Count).IsEqualTo(1); - await Assert.That(vm.ActiveItem).IsEqualTo(test1); - await Assert.That(view.ItemList.Items.Count).IsEqualTo(1); - } - } - - /// - /// Ensures view resolution respects contracts and fallback behavior. - /// - /// A representing the asynchronous operation. - [Test] - [TestExecutor] - public async Task ViewModelHostViewTestFallback() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - - using (resolver.WithResolver()) - { - await ResolveViewBIfViewBIsRegistered(resolver); - await ResolveView0WithFallback(resolver); - await ResolveNoneWithFallbackBypass(resolver); - } - - async Task ResolveViewBIfViewBIsRegistered(ModernDependencyResolver r) - { - r.Register( - () => new FakeViewWithContract.View0(), - typeof(IViewFor)); - r.Register( - () => new FakeViewWithContract.ViewA(), - typeof(IViewFor), - FakeViewWithContract.ContractA); - r.Register( - () => new FakeViewWithContract.ViewB(), - typeof(IViewFor), - FakeViewWithContract.ContractB); - - var vm = new FakeViewWithContract.MyViewModel(); - var host = new ViewModelViewHost { ViewModel = vm, ViewContract = FakeViewWithContract.ContractB, }; - - // Simulate activation by raising the Loaded event - var loaded = new RoutedEventArgs { RoutedEvent = FrameworkElement.LoadedEvent }; - host.RaiseEvent(loaded); - - await Assert.That(host.Content).IsNotNull(); - await Assert.That(host.Content).IsAssignableTo(); - } - - async Task ResolveView0WithFallback(ModernDependencyResolver r) - { - r.UnregisterCurrent( - typeof(IViewFor), - FakeViewWithContract.ContractB); - - var vm = new FakeViewWithContract.MyViewModel(); - var host = new ViewModelViewHost - { - ViewModel = vm, - ViewContract = FakeViewWithContract.ContractB, - ContractFallbackByPass = false, - }; - - // Simulate activation by raising the Loaded event - var loaded = new RoutedEventArgs { RoutedEvent = FrameworkElement.LoadedEvent }; - host.RaiseEvent(loaded); - - await Assert.That(host.Content).IsNotNull(); - await Assert.That(host.Content).IsAssignableTo(); - } - - async Task ResolveNoneWithFallbackBypass(ModernDependencyResolver r) - { - r.UnregisterCurrent( - typeof(IViewFor), - FakeViewWithContract.ContractB); - - var vm = new FakeViewWithContract.MyViewModel(); - var host = new ViewModelViewHost - { - ViewModel = vm, - ViewContract = FakeViewWithContract.ContractB, - ContractFallbackByPass = true, - }; - - // Simulate activation by raising the Loaded event - var loaded = new RoutedEventArgs { RoutedEvent = FrameworkElement.LoadedEvent }; - host.RaiseEvent(loaded); - - await Assert.That(host.Content).IsNull(); - } - } - - /// - /// Verifies the dummy suspension driver calls. - /// - /// A representing the asynchronous operation. - [Test] - public async Task DummySuspensionDriverTest() - { - var dsd = new DummySuspensionDriver(); - dsd.LoadState().Select(static _ => 1).Subscribe(async static v => await Assert.That(v).IsEqualTo(1)); - dsd.SaveState("Save Me").Select(static _ => 2).Subscribe(async static v => await Assert.That(v).IsEqualTo(2)); - dsd.InvalidateState().Select(static _ => 3).Subscribe(async static v => await Assert.That(v).IsEqualTo(3)); - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/RandomTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/RandomTests.cs deleted file mode 100644 index e39b49305a..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/RandomTests.cs +++ /dev/null @@ -1,885 +0,0 @@ -// Copyright (c) 2025 .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; - -using ReactiveUI.Tests.Infrastructure.StaticState; - -namespace ReactiveUI.Tests.Core; - -[NotInParallel] -public class RandomTests : IDisposable -{ - private MessageBusScope? _messageBusScope; - - [Before(Test)] - public void SetUp() - { - _messageBusScope = new MessageBusScope(); - } - - [After(Test)] - public void TearDown() - { - _messageBusScope?.Dispose(); - } - - [Test] - public async Task StringConverterAffinityTest() - { - var fixture = new StringConverter(); - var result = fixture.GetAffinityForObjects( - typeof(object), - typeof(string)); - await Assert.That(result).IsEqualTo(2); - result = fixture.GetAffinityForObjects( - typeof(object), - typeof(int)); - await Assert.That(result).IsEqualTo(0); - } - - [Test] - public async Task StringConverterTryConvertTest() - { - var fixture = new StringConverter(); - var expected = fixture.GetType().FullName; - var result = fixture.TryConvert( - fixture, - typeof(string), - null, - out var actualResult); - using (Assert.Multiple()) - { - await Assert.That(result).IsTrue(); - await Assert.That(actualResult).IsEqualTo(expected); - } - } - - [Test] - public async Task UnhandledErrorExceptionTest() - { - var fixture = new UnhandledErrorException(); - await Assert.That(fixture.Message).IsEqualTo("Exception of type 'ReactiveUI.UnhandledErrorException' was thrown."); - } - - [Test] - public async Task UnhandledErrorExceptionTestWithMessage() - { - var fixture = new UnhandledErrorException("We are terribly sorry but a unhandled error occured."); - await Assert.That(fixture.Message).IsEqualTo("We are terribly sorry but a unhandled error occured."); - } - - [Test] - public async Task UnhandledErrorExceptionTestWithMessageAndInnerException() - { - var fixture = new UnhandledErrorException( - "We are terribly sorry but a unhandled error occured.", - new Exception("Inner Exception added.")); - using (Assert.Multiple()) - { - await Assert.That(fixture.Message).IsEqualTo("We are terribly sorry but a unhandled error occured."); - await Assert.That(fixture.InnerException?.Message).IsEqualTo("Inner Exception added."); - } - } - - [Test] - public async Task ViewLocatorNotFoundExceptionTest() - { - var fixture = new ViewLocatorNotFoundException(); - await Assert.That(fixture.Message).IsEqualTo("Exception of type 'ReactiveUI.ViewLocatorNotFoundException' was thrown."); - } - - [Test] - public async Task ViewLocatorNotFoundExceptionTestWithMessage() - { - var fixture = new ViewLocatorNotFoundException("We are terribly sorry but the View Locator was Not Found."); - await Assert.That(fixture.Message).IsEqualTo("We are terribly sorry but the View Locator was Not Found."); - } - - [Test] - public async Task ViewLocatorNotFoundExceptionTestWithMessageAndInnerException() - { - var fixture = new ViewLocatorNotFoundException( - "We are terribly sorry but the View Locator was Not Found.", - new Exception("Inner Exception added.")); - using (Assert.Multiple()) - { - await Assert.That(fixture.Message).IsEqualTo("We are terribly sorry but the View Locator was Not Found."); - await Assert.That(fixture.InnerException?.Message).IsEqualTo("Inner Exception added."); - } - } - - [Test] - public async Task ViewLocatorCurrentUsesAppLocatorTest() - { - // Ensure RxApp is initialized so IViewLocator is registered - RxApp.EnsureInitialized(); - - // Verify that ViewLocator.Current retrieves from AppLocator - var fromViewLocator = ViewLocator.Current; - var fromAppLocator = AppLocator.Current.GetService(); - - using (Assert.Multiple()) - { - await Assert.That(fromViewLocator).IsNotNull(); - await Assert.That(fromAppLocator).IsNotNull(); - await Assert.That(fromViewLocator).IsSameReferenceAs(fromAppLocator); - } - } - - [Test] - public async Task ViewLocatorCurrentTest() - { - RxApp.EnsureInitialized(); - var fixture = ViewLocator.Current; - await Assert.That(fixture).IsNotNull(); - } - - /// - /// Tests that ViewContractAttribute correctly stores contract value. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ViewContractAttribute_ShouldStoreContractValue() - { - // Arrange - const string expectedContract = "TestContract"; - - // Act - var attribute = new ViewContractAttribute(expectedContract); - - // Assert - await Assert.That(attribute.Contract).IsEqualTo(expectedContract); - } - - /// - /// Tests that ViewContractAttribute handles null contract. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ViewContractAttribute_ShouldHandleNullContract() - { - // Act - var attribute = new ViewContractAttribute(null!); - - // Assert - await Assert.That(attribute.Contract).IsNull(); - } - - /// - /// Tests TriggerUpdate enum values. - /// - /// A representing the asynchronous operation. - [Test] - [SuppressMessage("Usage", "TUnitAssertions0005:Assert.That(...) should not be used with a constant value", Justification = "Verifying enum values remain constant for backwards compatibility")] - public async Task TriggerUpdate_ShouldHaveCorrectValues() - { - using (Assert.Multiple()) - { - // Assert - await Assert.That((int)TriggerUpdate.ViewToViewModel).IsEqualTo(0); - await Assert.That((int)TriggerUpdate.ViewModelToView).IsEqualTo(1); - } - } - - /// - /// Tests BindingDirection enum values. - /// - /// A representing the asynchronous operation. - [Test] - [SuppressMessage("Usage", "TUnitAssertions0005:Assert.That(...) should not be used with a constant value", Justification = "Verifying enum values remain constant for backwards compatibility")] - public async Task BindingDirection_ShouldHaveCorrectValues() - { - using (Assert.Multiple()) - { - // Assert - await Assert.That((int)BindingDirection.OneWay).IsEqualTo(0); - await Assert.That((int)BindingDirection.TwoWay).IsEqualTo(1); - await Assert.That((int)BindingDirection.AsyncOneWay).IsEqualTo(2); - } - } - - /// - /// Tests ObservedChange constructor and properties. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservedChange_ShouldStoreValues() - { - // Arrange - const string sender = "test sender"; - var expression = System.Linq.Expressions.Expression.Constant("test"); - const int value = 42; - - // Act - var observedChange = new ObservedChange( - sender, - expression, - value); - - using (Assert.Multiple()) - { - // Assert - await Assert.That(observedChange.Sender).IsEqualTo(sender); - await Assert.That(observedChange.Expression).IsEqualTo(expression); - await Assert.That(observedChange.Value).IsEqualTo(value); - } - } - - /// - /// Tests ReactivePropertyChangedEventArgs constructor and properties. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ReactivePropertyChangedEventArgs_ShouldStoreValues() - { - // Arrange - const string sender = "test sender"; - const string propertyName = "TestProperty"; - - // Act - var eventArgs = new ReactivePropertyChangedEventArgs( - sender, - propertyName); - - using (Assert.Multiple()) - { - // Assert - await Assert.That(eventArgs.Sender).IsEqualTo(sender); - await Assert.That(eventArgs.PropertyName).IsEqualTo(propertyName); - } - } - - /// - /// Tests EqualityTypeConverter with matching types. - /// - /// A representing the asynchronous operation. - [Test] - public async Task EqualityTypeConverter_ShouldConvertMatchingTypes() - { - // Arrange - var converter = new EqualityTypeConverter(); - const string testValue = "test"; - - // Act - var affinity = converter.GetAffinityForObjects( - typeof(string), - typeof(string)); - var result = converter.TryConvert( - testValue, - typeof(string), - null, - out var converted); - - using (Assert.Multiple()) - { - // Assert - await Assert.That(affinity).IsEqualTo(100); - await Assert.That(result).IsTrue(); - await Assert.That(converted).IsEqualTo(testValue); - } - } - - /// - /// Tests RxApp cache constants. - /// - /// A representing the asynchronous operation. - [Test] - [SuppressMessage("Usage", "TUnitAssertions0005:Assert.That(...) should not be used with a constant value", Justification = "Verifying cache constants remain unchanged for backwards compatibility")] - public async Task RxApp_ShouldHaveCacheConstants() - { - using (Assert.Multiple()) - { - // Assert -#if ANDROID || IOS - Assert.That(RxApp.SmallCacheLimit, Is.EqualTo(32)); - Assert.That(RxApp.BigCacheLimit, Is.EqualTo(64)); -#else - await Assert.That(RxApp.SmallCacheLimit).IsEqualTo(64); - await Assert.That(RxApp.BigCacheLimit).IsEqualTo(256); - } -#endif - } - - /// - /// Tests various type converters affinity. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TypeConverters_ShouldHaveCorrectAffinity() - { - // Arrange & Act - var intConverter = new IntegerToStringTypeConverter(); - var doubleConverter = new DoubleToStringTypeConverter(); - - using (Assert.Multiple()) - { - // Assert - These converters return 10 for their type conversions - await Assert.That(intConverter.GetAffinityForObjects( - typeof(int), - typeof(string))).IsEqualTo(10); - await Assert.That(doubleConverter.GetAffinityForObjects( - typeof(double), - typeof(string))).IsEqualTo(10); - await Assert.That(intConverter.GetAffinityForObjects( - typeof(string), - typeof(int))).IsEqualTo(10); - } - } - - /// - /// Tests DummySuspensionDriver methods. - /// - /// A representing the asynchronous operation. - [Test] - public async Task DummySuspensionDriver_ShouldWork() - { - // Arrange - var driver = new DummySuspensionDriver(); - var state = new { TestProperty = "test" }; - - using (Assert.Multiple()) - { - // Act & Assert - await Assert.That(driver.InvalidateState()).IsNotNull(); - await Assert.That(driver.LoadState()).IsNotNull(); - await Assert.That(driver.SaveState(state)).IsNotNull(); - } - } - - /// - /// Tests RegistrationNamespace enum values. - /// - /// A representing the asynchronous operation. - [Test] - [SuppressMessage("Usage", "TUnitAssertions0005:Assert.That(...) should not be used with a constant value", Justification = "Verifying enum values remain constant for backwards compatibility")] - public async Task RegistrationNamespace_ShouldHaveAllExpectedValues() - { - var expectedValues = new[] - { - RegistrationNamespace.None, RegistrationNamespace.Winforms, RegistrationNamespace.Wpf, - RegistrationNamespace.Uno, RegistrationNamespace.UnoWinUI, RegistrationNamespace.Blazor, - RegistrationNamespace.Drawing, RegistrationNamespace.Avalonia, RegistrationNamespace.Maui, - RegistrationNamespace.Uwp, RegistrationNamespace.WinUI, - }; - - var actualValues = Enum.GetValues(); - await Assert.That(actualValues).IsEquivalentTo(expectedValues); - - using (Assert.Multiple()) - { - await Assert.That((int)RegistrationNamespace.None).IsEqualTo(0); - await Assert.That((int)RegistrationNamespace.Winforms).IsEqualTo(1); - await Assert.That((int)RegistrationNamespace.Wpf).IsEqualTo(2); - await Assert.That((int)RegistrationNamespace.Uno).IsEqualTo(3); - await Assert.That((int)RegistrationNamespace.UnoWinUI).IsEqualTo(4); - await Assert.That((int)RegistrationNamespace.Blazor).IsEqualTo(5); - await Assert.That((int)RegistrationNamespace.Drawing).IsEqualTo(6); - await Assert.That((int)RegistrationNamespace.Avalonia).IsEqualTo(7); - await Assert.That((int)RegistrationNamespace.Maui).IsEqualTo(8); - await Assert.That((int)RegistrationNamespace.Uwp).IsEqualTo(9); - await Assert.That((int)RegistrationNamespace.WinUI).IsEqualTo(10); - } - } - - /// - /// Tests type converter conversions with actual values. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TypeConverters_ShouldConvertValues() - { - // Arrange - var intConverter = new IntegerToStringTypeConverter(); - var doubleConverter = new DoubleToStringTypeConverter(); - - using (Assert.Multiple()) - { - // Act & Assert - await Assert.That(intConverter.TryConvert( - 42, - typeof(string), - null, - out var intResult)).IsTrue(); - await Assert.That(intResult).IsEqualTo("42"); - - await Assert.That(intConverter.TryConvert( - "42", - typeof(int), - null, - out var intBackResult)).IsTrue(); - await Assert.That(intBackResult).IsEqualTo(42); - - await Assert.That(doubleConverter.TryConvert( - 42.5, - typeof(string), - null, - out var doubleResult)).IsTrue(); - await Assert.That(doubleResult).IsEqualTo("42.5"); - } - } - - /// - /// Tests PreserveAttribute instantiation. - /// - /// A representing the asynchronous operation. - [Test] - public async Task PreserveAttribute_ShouldInstantiate() - { - // Act - var attribute = new PreserveAttribute(); - - // Assert - await Assert.That(attribute).IsNotNull(); - await Assert.That(attribute).IsTypeOf(); - } - - /// - /// Tests MessageBus.Current static property for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task MessageBus_Current_ShouldBeAccessible() - { - // Act - var current = MessageBus.Current; - - // Assert - await Assert.That(current).IsNotNull(); - await Assert.That(current).IsAssignableTo(); - } - - /// - /// Tests NotAWeakReference functionality for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task NotAWeakReference_ShouldWorkCorrectly() - { - // Arrange - const string target = "test target"; - var weakRef = new NotAWeakReference(target); - - using (Assert.Multiple()) - { - // Act & Assert - await Assert.That(weakRef.Target).IsEqualTo(target); - await Assert.That(weakRef.IsAlive).IsTrue(); - } - - // NotAWeakReference always holds strong reference - GC.Collect(); - GC.WaitForPendingFinalizers(); - - using (Assert.Multiple()) - { - await Assert.That(weakRef.IsAlive).IsTrue(); - await Assert.That(weakRef.Target).IsEqualTo(target); - } - } - - /// - /// Tests SingletonPropertyChangedEventArgs static properties for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SingletonPropertyChangedEventArgs_StaticProperties_ShouldWork() - { - // Act & Assert - await Assert.That(SingletonPropertyChangedEventArgs.Value).IsNotNull(); - using (Assert.Multiple()) - { - await Assert.That(SingletonPropertyChangedEventArgs.Value.PropertyName).IsEqualTo("Value"); - - await Assert.That(SingletonPropertyChangedEventArgs.HasErrors).IsNotNull(); - } - - using (Assert.Multiple()) - { - await Assert.That(SingletonPropertyChangedEventArgs.HasErrors.PropertyName).IsEqualTo("HasErrors"); - - await Assert.That(SingletonPropertyChangedEventArgs.ErrorMessage).IsNotNull(); - } - - await Assert.That(SingletonPropertyChangedEventArgs.ErrorMessage.PropertyName).IsEqualTo("ErrorMessage"); - } - - /// - /// Tests SingletonDataErrorsChangedEventArgs static properties for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SingletonDataErrorsChangedEventArgs_StaticProperties_ShouldWork() - { - // Act & Assert - await Assert.That(SingletonDataErrorsChangedEventArgs.Value).IsNotNull(); - await Assert.That(SingletonDataErrorsChangedEventArgs.Value.PropertyName).IsEqualTo("Value"); - } - - /// - /// Tests ViewContractAttribute attribute usage for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ViewContractAttribute_ShouldHaveCorrectAttributeUsage() - { - // Arrange - var attributeType = typeof(ViewContractAttribute); - - // Act - var attributeUsage = attributeType.GetCustomAttribute(); - - // Assert - await Assert.That(attributeUsage).IsNotNull(); - await Assert.That(attributeUsage.ValidOn).IsEqualTo(AttributeTargets.Class); - } - - /// - /// Tests SingleInstanceViewAttribute for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SingleInstanceViewAttribute_ShouldWork() - { - // Act - var attribute = new SingleInstanceViewAttribute(); - - // Assert - await Assert.That(attribute).IsNotNull(); - await Assert.That(attribute).IsTypeOf(); - - // Test attribute usage - var attributeType = typeof(SingleInstanceViewAttribute); - var attributeUsage = attributeType.GetCustomAttribute(); - await Assert.That(attributeUsage).IsNotNull(); - await Assert.That(attributeUsage.ValidOn).IsEqualTo(AttributeTargets.Class); - } - - /// - /// Tests LocalizableAttribute for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task LocalizableAttribute_ShouldWork() - { - // Act - var localizableTrue = new LocalizableAttribute(true); - var localizableFalse = new LocalizableAttribute(false); - - using (Assert.Multiple()) - { - // Assert - await Assert.That(localizableTrue).IsNotNull(); - await Assert.That(localizableFalse).IsNotNull(); - } - - using (Assert.Multiple()) - { - await Assert.That(localizableTrue.IsLocalizable).IsTrue(); - await Assert.That(localizableFalse.IsLocalizable).IsFalse(); - } - } - - /// - /// Tests WhenAnyMixin functionality for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task WhenAnyMixin_ShouldWork() - { - // Arrange - var testObject = new TestFixture { IsOnlyOneWord = "Initial" }; - var changes = new List(); - - // Act - testObject.WhenAnyValue(x => x.IsOnlyOneWord) - .Subscribe(x => changes.Add(x ?? string.Empty)); - - testObject.IsOnlyOneWord = "Updated"; - - // Assert - await Assert.That(changes).Count().IsEqualTo(2); // Initial + Updated - using (Assert.Multiple()) - { - await Assert.That(changes[0]).IsEqualTo("Initial"); - await Assert.That(changes[1]).IsEqualTo("Updated"); - } - } - - /// - /// Tests ObservableAsPropertyHelper functionality for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableAsPropertyHelper_ShouldWork() - { - // Arrange - var subject = new Subject(); - var testObject = new OaphTestFixture(); - - // Create OAPH - subject.ToProperty( - testObject, - x => x.FirstName, - out var helper); - - testObject.FirstNameHelper = helper; - - // Act - subject.OnNext("John"); - - using (Assert.Multiple()) - { - // Assert - await Assert.That(testObject.FirstName).IsEqualTo("John"); - await Assert.That(testObject.FirstNameHelper!.Value).IsEqualTo("John"); - } - } - - /// - /// Tests ReactiveNotifyPropertyChangedMixin functionality for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ReactiveNotifyPropertyChangedMixin_ShouldWork() - { - // Arrange - var testObject = new AccountUser(); - var changes = new List(); - - // Act - testObject.GetChangedObservable() - .Subscribe(x => changes.Add(x.PropertyName ?? string.Empty)); - - testObject.Name = "Test User"; - - // Assert - await Assert.That(changes).Contains("Name"); - } - - /// - /// Tests ExpressionMixins functionality for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ExpressionMixins_ShouldWork() - { - // Arrange - Expression> expression = x => x.IsOnlyOneWord; - - // Act - var propertyName = expression.Body.GetMemberInfo()?.Name; - - // Assert - await Assert.That(propertyName).IsEqualTo("IsOnlyOneWord"); - } - - /// - /// Tests DisposableMixins functionality for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task DisposableMixins_ShouldWork() - { - // Arrange - var disposable1 = Disposable.Create(() => { }); - var disposable2 = Disposable.Create(() => { }); - var compositeDisposable = new CompositeDisposable(); - - // Act & Assert - Should not throw - disposable1.DisposeWith(compositeDisposable); - disposable2.DisposeWith(compositeDisposable); - - // Verify they are added to the composite - await Assert.That(compositeDisposable).Count().IsEqualTo(2); - } - - /// - /// Tests CompatMixins functionality for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CompatMixins_ShouldWork() - { - // Arrange - var items = new[] { 1, 2, 3, 4, 5 }; - var processedItems = new List(); - - // Act - items.Run(x => processedItems.Add(x * 2)); - var skippedLast = items.SkipLast(2).ToList(); - - using (Assert.Multiple()) - { - // Assert - await Assert.That(processedItems).IsEquivalentTo([2, 4, 6, 8, 10]); - await Assert.That(skippedLast).IsEquivalentTo([1, 2, 3]); - } - } - - /// - /// Tests ViewForMixins functionality for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ViewForMixins_ShouldWork() - { - // Arrange - var viewModel = new FakeViewModel { Name = "Test" }; - var view = new FakeView { ViewModel = viewModel }; - - using (Assert.Multiple()) - { - // Act - Test that the view correctly exposes the viewmodel - await Assert.That(view.ViewModel).IsEqualTo(viewModel); - await Assert.That(viewModel.Name).IsEqualTo("Test"); - } - - // Set property directly since binding setup might be complex in test environment - view.SomeProperty = viewModel.Name; - - // Assert - await Assert.That(view.SomeProperty).IsEqualTo("Test"); - } - - /// - /// Tests Observables static members for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Observables_StaticMembers_ShouldWork() - { - // Act & Assert - using (Assert.Multiple()) - { - await Assert.That(Observables.Unit).IsNotNull(); - await Assert.That(Observables.True).IsNotNull(); - await Assert.That(Observables.False).IsNotNull(); - } - - // Test that they emit expected values - bool? trueValue = null; - bool? falseValue = null; - Unit? unitValue = null; - - Observables.True.Subscribe(x => trueValue = x); - Observables.False.Subscribe(x => falseValue = x); - Observables.Unit.Subscribe(x => unitValue = x); - - using (Assert.Multiple()) - { - await Assert.That(trueValue).IsTrue(); - await Assert.That(falseValue).IsFalse(); - await Assert.That(unitValue).IsEqualTo(Unit.Default); - } - } - - /// - /// Tests additional type converters for 100% coverage. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AdditionalTypeConverters_ShouldWork() - { - // Test all the other type converters - var converters = new IBindingTypeConverter[] - { - new DecimalToStringTypeConverter(), new ByteToStringTypeConverter(), new LongToStringTypeConverter(), - new SingleToStringTypeConverter(), new ShortToStringTypeConverter(), - new NullableDecimalToStringTypeConverter(), new NullableByteToStringTypeConverter(), - new NullableLongToStringTypeConverter(), new NullableSingleToStringTypeConverter(), - new NullableShortToStringTypeConverter() - }; - - // Test that they all have reasonable affinities - foreach (var converter in converters) - { - var affinity = converter.GetAffinityForObjects( - typeof(object), - typeof(string)); - await Assert.That(affinity).IsGreaterThanOrEqualTo(0); - } - } - - public void Dispose() - { - _messageBusScope?.Dispose(); - _messageBusScope = null; - } - - /// - /// Simple test fixture for property binding tests. - /// - private class TestFixture : ReactiveObject - { - private string? _isOnlyOneWord; - - public string? IsOnlyOneWord - { - get => _isOnlyOneWord; - set => this.RaiseAndSetIfChanged( - ref _isOnlyOneWord, - value); - } - } - - /// - /// Test fixture for OAPH tests. - /// - private class OaphTestFixture : ReactiveObject - { - public ObservableAsPropertyHelper? FirstNameHelper { get; set; } - - public string? FirstName => FirstNameHelper?.Value; - } - - /// - /// Test fixture for account user tests. - /// - private class AccountUser : ReactiveObject - { - private string? _name; - - public string? Name - { - get => _name; - set => this.RaiseAndSetIfChanged( - ref _name, - value); - } - } - - /// - /// Fake view model for testing. - /// - private class FakeViewModel : ReactiveObject - { - private string? _name; - - public string? Name - { - get => _name; - set => this.RaiseAndSetIfChanged( - ref _name, - value); - } - } - - /// - /// Fake view for testing. - /// - private class FakeView : IViewFor - { - public FakeViewModel? ViewModel { get; set; } - - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (FakeViewModel?)value; - } - - public string? SomeProperty { get; set; } - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/RxAppTest.cs b/src/tests/ReactiveUI.NonParallel.Tests/RxAppTest.cs deleted file mode 100644 index 5fde8464eb..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/RxAppTest.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2025 .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 ReactiveUI.Tests.Infrastructure.StaticState; - -namespace ReactiveUI.Tests.Core; - -[NotInParallel] -public class RxAppTest : IDisposable -{ - private RxAppSchedulersScope? _schedulersScope; - - [Before(Test)] - public void SetUp() - { - _schedulersScope = new RxAppSchedulersScope(); - } - - [After(Test)] - public void TearDown() - { - _schedulersScope?.Dispose(); - } - - /// - /// Tests that schedulers should be current thread in test runner. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SchedulerShouldBeCurrentThreadInTestRunner() - { - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(CurrentThreadScheduler.Instance); - } - - public void Dispose() - { - TearDown(); - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Testing/AppBuilderTestBaseTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/Testing/AppBuilderTestBaseTests.cs deleted file mode 100644 index 21a13130da..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Testing/AppBuilderTestBaseTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2025 .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.Testing; - -namespace ReactiveUI.Tests.Testing; - -/// -/// Tests for AppBuilderTestBase. -/// -[NotInParallel] -public class AppBuilderTestBaseTests -{ - /// - /// Tests that RunAppBuilderTestAsync with Func<Task> executes the test body. - /// - /// A representing the asynchronous operation. - [Test] - public async Task RunAppBuilderTestAsync_WithTaskFunc_ExecutesTestBody() - { - var executed = false; - - await TestAppBuilderTest.TestWithTaskFunc(async () => - { - executed = true; - await Task.CompletedTask; - }); - - await Assert.That(executed).IsTrue(); - } - - /// - /// Tests that RunAppBuilderTestAsync with Action executes the test body. - /// - /// A representing the asynchronous operation. - [Test] - public async Task RunAppBuilderTestAsync_WithAction_ExecutesTestBody() - { - var executed = false; - - await TestAppBuilderTest.TestWithAction(() => executed = true); - - await Assert.That(executed).IsTrue(); - } - - /// - /// Tests that RunAppBuilderTestAsync with Action converts to Task correctly. - /// - /// A representing the asynchronous operation. - [Test] - public async Task RunAppBuilderTestAsync_WithAction_ConvertsToTask() - { - var counter = 0; - - var task = TestAppBuilderTest.TestWithAction(() => counter++); - - await task; - - await Assert.That(counter).IsEqualTo(1); - } - - /// - /// Test implementation that inherits from AppBuilderTestBase. - /// - private sealed class TestAppBuilderTest : AppBuilderTestBase - { - /// - /// Test method that calls RunAppBuilderTestAsync with a Task-returning function. - /// - /// The test body to execute. - /// A representing the asynchronous operation. - public static Task TestWithTaskFunc(Func testBody) => - RunAppBuilderTestAsync(testBody); - - /// - /// Test method that calls RunAppBuilderTestAsync with an Action. - /// - /// The test body to execute. - /// A representing the asynchronous operation. - public static Task TestWithAction(Action testBody) => - RunAppBuilderTestAsync(testBody); - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Testing/MessageBusExtensionsTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/Testing/MessageBusExtensionsTests.cs deleted file mode 100644 index 75ef176442..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Testing/MessageBusExtensionsTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2025 .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.Testing; - -namespace ReactiveUI.Tests.Testing; - -/// -/// Tests for MessageBusExtensions. -/// -[NotInParallel] -public class MessageBusExtensionsTests -{ - private MessageBusScope _scope = null!; - - [Before(Test)] - public void SetUp() => _scope = new MessageBusScope(); - - [After(Test)] - public void TearDown() => _scope.Dispose(); - - [Test] - public async Task With_Action_Executes_Block_With_Custom_MessageBus() - { - var customBus = new MessageBus(); - var executed = false; - - customBus.With(() => executed = true); - - await Assert.That(executed).IsTrue(); - } - - [Test] - public async Task With_Func_Executes_Block_With_Custom_MessageBus_And_Returns_Value() - { - var customBus = new MessageBus(); - var result = customBus.With(() => 42); - - await Assert.That(result).IsEqualTo(42); - } - - [Test] - public void With_Action_Throws_When_Block_Is_Null() - { - var customBus = new MessageBus(); - Assert.Throws(() => customBus.With((Action)null!)); - } - - [Test] - public void With_Func_Throws_When_Block_Is_Null() - { - var customBus = new MessageBus(); - Assert.Throws(() => customBus.With((Func)null!)); - } - - [Test] - public async Task WithMessageBus_Restores_Original_MessageBus_After_Disposal() - { - var originalBus = MessageBus.Current; - var customBus = new MessageBus(); - - using (customBus.WithMessageBus()) - { - await Assert.That(MessageBus.Current).IsSameReferenceAs(customBus); - } - - await Assert.That(MessageBus.Current).IsSameReferenceAs(originalBus); - } - - private sealed class MessageBusScope : IDisposable - { - private readonly IMessageBus _originalMessageBus; - - public MessageBusScope() => _originalMessageBus = MessageBus.Current; - - public void Dispose() => MessageBus.Current = _originalMessageBus; - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Testing/RxTestTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/Testing/RxTestTests.cs deleted file mode 100644 index db52a5f308..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/Testing/RxTestTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2025 .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.Testing; - -namespace ReactiveUI.Tests.Testing; - -/// -/// Tests for RxTest. -/// -[NotInParallel] -public class RxTestTests -{ - [Test] - public async Task AppBuilderTestAsync_Executes_Test_Body() - { - var executed = false; - - await RxTest.AppBuilderTestAsync(async () => - { - executed = true; - await Task.CompletedTask; - }); - - await Assert.That(executed).IsTrue(); - } - - [Test] - public void AppBuilderTestAsync_Throws_When_TestBody_Is_Null() - { - Assert.ThrowsAsync(async () => - await RxTest.AppBuilderTestAsync(null!)); - } - - [Test] - public async Task AppBuilderTestAsync_Propagates_Exceptions_From_TestBody() - { - var exception = await Assert.ThrowsAsync(async () => - await RxTest.AppBuilderTestAsync(() => throw new InvalidOperationException("Test exception"))); - - await Assert.That(exception!.Message).IsEqualTo("Test exception"); - } - - [Test] - public async Task AppBuilderTestAsync_Throws_TimeoutException_When_TestBody_Exceeds_Timeout() - { - var exception = await Assert.ThrowsAsync(async () => - await RxTest.AppBuilderTestAsync( - async () => await Task.Delay(200), - maxWaitMs: 50)); - - await Assert.That(exception!.Message).Contains("Test execution exceeded"); - } - - [Test] - public async Task AppBuilderTestAsync_Resets_Builder_State_After_Test() - { - await RxTest.AppBuilderTestAsync(async () => - { - // Test body - await Task.CompletedTask; - }); - - // If we reach here without hanging, the builder state was reset correctly - } -} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/WhenAny/WhenAnyObservableTests.cs b/src/tests/ReactiveUI.NonParallel.Tests/WhenAny/WhenAnyObservableTests.cs deleted file mode 100644 index f1a27733a8..0000000000 --- a/src/tests/ReactiveUI.NonParallel.Tests/WhenAny/WhenAnyObservableTests.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) 2025 .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 DynamicData; - -namespace ReactiveUI.Tests; - -/// -/// Tests for WhenAnyObservable functionality. -/// This test class is marked as NotInParallel because WhenAnyObservable relies on -/// the service locator (Locator.Current) to find ICreatesObservableForProperty implementations. -/// When tests run in parallel, they can interfere with each other's service locator state, -/// causing intermittent failures with "Could not find a ICreatesObservableForProperty" errors. -/// -[NotInParallel] -public class WhenAnyObservableTests -{ - /// - /// Tests that null observables do not cause exceptions. - /// - [Test] - public void NullObservablesDoNotCauseExceptions() - { - var fixture = new TestWhenAnyObsViewModel - { - Command1 = null - }; - - // these are the 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(static x => x.Command1, 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, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, 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, 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, static x => x.Command1, 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, static x => x.Command1, static x => x.Command1, 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, static x => x.Command1, static x => x.Command1, static x => x.Command1, 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, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, 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, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); - - // these are the overloads of WhenAnyObservable that perform a CombineLatest - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static (zero, one) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two, three) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two, three, four) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two, three, four, five) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two, three, four, five, six) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two, three, four, five, six, seven) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two, three, four, five, six, seven, eight) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two, three, four, five, six, seven, eight, nine) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two, three, four, five, six, seven, eight, nine, ten) => Unit.Default).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (zero, one, two, three, four, five, six, seven, eight, nine, ten, eleven) => Unit.Default).Subscribe(); - } - - /// - /// Performs a smoke test on combining WhenAnyObservable. - /// - /// A task to monitor the progress. - [Test] - 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(ImmediateScheduler.Instance).Subscribe(list.Add); - await Assert.That(list).IsEmpty(); - - await fixture.Command1!.Execute(1); - await fixture.Command3.Execute("foo"); - await Assert.That(list).Count().IsEqualTo(1); - - await fixture.Command1.Execute(2); - await Assert.That(list).Count().IsEqualTo(2); - - await fixture.Command3.Execute("bar"); - using (Assert.Multiple()) - { - await Assert.That(list).Count().IsEqualTo(3); - - await Assert.That(new[] { "foo : 1", "foo : 2", "bar : 2", }.Zip( - list, - static (expected, actual) => new - { - expected, - actual - }).All(static x => x.expected == x.actual)).IsTrue(); - } - } - - /// - /// Performs a smoke test testing WhenAnyObservable merging results. - /// - /// A task to monitor the progress. - [Test] - public async Task WhenAnyObservableSmokeTestMerging() - { - var fixture = new TestWhenAnyObsViewModel(); - - var list = new List(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command2).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - await Assert.That(list).IsEmpty(); - - await fixture.Command1!.Execute(1); - await Assert.That(list).Count().IsEqualTo(1); - - await fixture.Command2.Execute(2); - await Assert.That(list).Count().IsEqualTo(2); - - await fixture.Command1.Execute(1); - using (Assert.Multiple()) - { - await Assert.That(list).Count().IsEqualTo(3); - - await Assert.That(new[] { 1, 2, 1, }.Zip( - list, - static (expected, actual) => new - { - expected, - actual - }).All(static x => x.expected == x.actual)).IsTrue(); - } - } - - /// - /// Tests WhenAnyObservable with null object should update when object isnt null anymore. - /// - /// A representing the asynchronous operation. - [Test] - public async Task WhenAnyObservableWithNullObjectShouldUpdateWhenObjectIsntNullAnymore() - { - var fixture = new TestWhenAnyObsViewModel(); - fixture!.WhenAnyObservable(static x => x.Changes)!.Bind(out var output).ObserveOn(ImmediateScheduler.Instance).Subscribe(); - await Assert.That(output).IsEmpty(); - - fixture.MyListOfInts = []; - await Assert.That(output).IsEmpty(); - - fixture.MyListOfInts.Add(1); - await Assert.That(output).Count().IsEqualTo(1); - - fixture.MyListOfInts = null; - await Assert.That(output).Count().IsEqualTo(1); - } -} diff --git a/src/tests/ReactiveUI.Splat.Tests/SplatAdapterTests.cs b/src/tests/ReactiveUI.Splat.Tests/SplatAdapterTests.cs index 17a8b78c94..51d4ec8c99 100644 --- a/src/tests/ReactiveUI.Splat.Tests/SplatAdapterTests.cs +++ b/src/tests/ReactiveUI.Splat.Tests/SplatAdapterTests.cs @@ -11,6 +11,8 @@ using Ninject; +using ReactiveUI.Builder; + using Splat.Autofac; using Splat.DryIoc; using Splat.Ninject; @@ -33,7 +35,9 @@ public async Task DryIocDependencyResolver_Should_Register_ReactiveUI_BindingTyp // Invoke RxApp which initializes the ReactiveUI platform. var container = new Container(); container.UseDryIocDependencyResolver(); - Locator.CurrentMutable.InitializeReactiveUI(); + Locator.CurrentMutable.CreateReactiveUIBuilder() + .WithCoreServices() + .Build(); var converters = container.Resolve>().ToList(); @@ -55,7 +59,9 @@ public async Task DryIocDependencyResolver_Should_Register_ReactiveUI_CreatesCom // Invoke RxApp which initializes the ReactiveUI platform. var container = new Container(); container.UseDryIocDependencyResolver(); - Locator.CurrentMutable.InitializeReactiveUI(); + Locator.CurrentMutable.CreateReactiveUIBuilder() + .WithCoreServices() + .Build(); var converters = container.Resolve>().ToList(); @@ -77,7 +83,9 @@ 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.InitializeReactiveUI(); + locator.CreateReactiveUIBuilder() + .WithCoreServices() + .Build(); var container = builder.Build(); var converters = container.Resolve>().ToList(); @@ -100,7 +108,9 @@ 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.InitializeReactiveUI(); + locator.CreateReactiveUIBuilder() + .WithCoreServices() + .Build(); Locator.SetLocator(locator); var container = builder.Build(); @@ -124,7 +134,9 @@ public async Task NinjectDependencyResolver_Should_Register_ReactiveUI_BindingTy // Invoke RxApp which initializes the ReactiveUI platform. var container = new StandardKernel(); container.UseNinjectDependencyResolver(); - Locator.CurrentMutable.InitializeReactiveUI(); + Locator.CurrentMutable.CreateReactiveUIBuilder() + .WithCoreServices() + .Build(); var converters = container.GetAll().ToList(); @@ -146,7 +158,9 @@ public async Task NinjectDependencyResolver_Should_Register_ReactiveUI_CreatesCo // Invoke RxApp which initializes the ReactiveUI platform. var container = new StandardKernel(); container.UseNinjectDependencyResolver(); - Locator.CurrentMutable.InitializeReactiveUI(); + Locator.CurrentMutable.CreateReactiveUIBuilder() + .WithCoreServices() + .Build(); var converters = container.GetAll().ToList(); diff --git a/src/tests/ReactiveUI.Tests/Utilities/ApiExtensions.cs b/src/tests/ReactiveUI.Test.Utilities/ApiExtensions.cs similarity index 62% rename from src/tests/ReactiveUI.Tests/Utilities/ApiExtensions.cs rename to src/tests/ReactiveUI.Test.Utilities/ApiExtensions.cs index 4f1753f552..c6086cb890 100644 --- a/src/tests/ReactiveUI.Tests/Utilities/ApiExtensions.cs +++ b/src/tests/ReactiveUI.Test.Utilities/ApiExtensions.cs @@ -1,33 +1,34 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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; using System.Runtime.CompilerServices; - using PublicApiGenerator; - using VerifyTUnit; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Utilities; /// -/// A helper for doing API approvals. +/// A helper for doing API approvals. /// [ExcludeFromCodeCoverage] public static class ApiExtensions { /// - /// Checks to make sure the API is approved. + /// Checks to make sure the API is approved. /// /// The assembly that is being checked. /// The namespaces. /// The caller file path. /// - /// A Task. + /// A Task. /// - public static async Task CheckApproval(this Assembly assembly, string[] namespaces, [CallerFilePath] string filePath = "") + public static async Task CheckApproval( + this Assembly assembly, + string[] namespaces, + [CallerFilePath] string filePath = "") { var generatorOptions = new ApiGeneratorOptions { AllowNamespacePrefixes = namespaces }; var apiText = assembly.GeneratePublicApi(generatorOptions); @@ -39,8 +40,14 @@ public static async Task CheckApproval(this Assembly assembly, string[] namespac l.StartsWith("[assembly: AssemblyFileVersion(", StringComparison.InvariantCulture) || l.StartsWith("[assembly: AssemblyInformationalVersion(", StringComparison.InvariantCulture) || l.StartsWith("[assembly: System.Reflection.AssemblyMetadata(", StringComparison.InvariantCulture) || - l.StartsWith("[assembly: System.Runtime.Versioning.SupportedOSPlatform(", StringComparison.InvariantCulture) || - l.StartsWith("[assembly: System.Runtime.Versioning.TargetFramework(", StringComparison.InvariantCulture) || - l.StartsWith("[assembly: System.Runtime.Versioning.TargetPlatform(", StringComparison.InvariantCulture)); + l.StartsWith( + "[assembly: System.Runtime.Versioning.SupportedOSPlatform(", + StringComparison.InvariantCulture) || + l.StartsWith( + "[assembly: System.Runtime.Versioning.TargetFramework(", + StringComparison.InvariantCulture) || + l.StartsWith( + "[assembly: System.Runtime.Versioning.TargetPlatform(", + StringComparison.InvariantCulture)); } } diff --git a/src/tests/ReactiveUI.Test.Utilities/AppBuilder/AppBuilderTestExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/AppBuilder/AppBuilderTestExecutor.cs new file mode 100644 index 0000000000..0c106dc8aa --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/AppBuilder/AppBuilderTestExecutor.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2025 .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.Utilities.AppBuilder; + +/// +/// Test executor that sets up AppBuilder isolation for test duration. +/// Ensures tests run serially and AppBuilder state is reset before/after each test. +/// +public class AppBuilderTestExecutor : ITestExecutor +{ + /// + public async ValueTask ExecuteTest(TestContext context, Func testAction) + { + ArgumentNullException.ThrowIfNull(testAction); + + // Force-reset any previous builder state to avoid waiting deadlocks. + RxAppBuilder.ResetForTesting(); + Splat.Builder.AppBuilder.ResetBuilderStateForTests(); + + // Re-initialize ReactiveUI with core services after reset + // This ensures tests have a clean, properly initialized environment + _ = RxAppBuilder.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); + + try + { + // Execute actual test with timeout so it doesn't hang forever on CI. + await testAction(); + } + finally + { + // Final reset after test and rebuild to ensure completely clean state for next test + RxAppBuilder.ResetForTesting(); + + // Rebuild to clear any service registrations made during the test + _ = RxAppBuilder.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); + } + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/Combined/WithSchedulerAndMessageBusExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/Combined/WithSchedulerAndMessageBusExecutor.cs new file mode 100644 index 0000000000..31336baf8d --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Combined/WithSchedulerAndMessageBusExecutor.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2025 .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.Utilities.Combined; + +/// +/// Test executor that sets up both scheduler and message bus overrides. +/// +public class WithSchedulerAndMessageBusExecutor : ITestExecutor +{ + /// + public async ValueTask ExecuteTest(TestContext context, Func testAction) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(testAction); + + var scheduler = ImmediateScheduler.Instance; + var messageBus = new ReactiveUI.MessageBus(); + var previousBus = ReactiveUI.MessageBus.Current; + + context.StateBag.Items["Scheduler"] = scheduler; + context.StateBag.Items["MessageBus"] = messageBus; + + RxAppBuilder.ResetForTesting(); + + _ = RxAppBuilder.CreateReactiveUIBuilder() + .WithMainThreadScheduler(scheduler) + .WithTaskPoolScheduler(scheduler) + .WithMessageBus(messageBus) + .WithCoreServices() + .BuildApp(); + + try + { + context.RestoreExecutionContext(); + await testAction(); + } + finally + { + ReactiveUI.MessageBus.Current = previousBus; + RxAppBuilder.ResetForTesting(); + } + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutor.cs new file mode 100644 index 0000000000..55613ca4a8 --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutor.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2025 .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 Splat; + +namespace ReactiveUI.Tests.Utilities.Logging; + +/// +/// Provides a test execution scope that configures a default logging infrastructure for unit tests requiring +/// ReactiveUI +/// logging services. +/// +/// +/// This class ensures that a default ILogManager and logger are registered for the duration of a test, +/// allowing tests to run without requiring explicit logging setup. The logging configuration is reset before and after +/// each test execution to prevent side effects between tests. +/// +public class LoggingRegistrationExecutor : ITestExecutor +{ + /// + public async ValueTask ExecuteTest(TestContext context, Func action) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(action); + + RxAppBuilder.ResetForTesting(); + + var logger = new TestLogger(); + var fullLogger = new WrappingFullLogger(logger); + + // Register a default ILogManager for tests + // This ensures ILogManager is available even if tests don't set up their own + var currentLogManager = new TestLogManager(fullLogger); + + context.StateBag["TestLogger"] = logger; + context.StateBag["TestLogManager"] = currentLogManager; + + _ = RxAppBuilder.CreateReactiveUIBuilder() + .WithRegistration(r => r.Register(() => currentLogManager)) + .WithCoreServices() + .BuildApp(); + + try + { + await action(); + } + finally + { + RxAppBuilder.ResetForTesting(); + } + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutorExtensions.cs b/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutorExtensions.cs new file mode 100644 index 0000000000..667b780892 --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutorExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2025 .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.Utilities.Logging; + +/// +/// Extension methods for retrieving logging-related test artifacts from a . +/// +public static class LoggingRegistrationExecutorExtensions +{ + extension(TestContext context) + { + /// + /// Retrieves the current test logger instance associated with the context, if one is available. + /// + /// A instance if a test logger is present in the context; otherwise, . + public TestLogger? GetTestLogger() + { + ArgumentNullException.ThrowIfNull(context); + return (TestLogger?)context.StateBag.Items["TestLogger"]; + } + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/Logging/TestLogManager.cs b/src/tests/ReactiveUI.Test.Utilities/Logging/TestLogManager.cs new file mode 100644 index 0000000000..8bd2787a97 --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Logging/TestLogManager.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2025 .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 Splat; + +namespace ReactiveUI.Tests.Utilities.Logging; + +/// +/// Provides a test implementation of the interface that always returns the same logger +/// instance. +/// +/// The logger instance to be returned by this log manager. Cannot be null. +public class TestLogManager(IFullLogger logger) : ILogManager +{ + /// + /// Gets a logger instance associated with the specified type. + /// + /// The type for which to retrieve the logger instance. + /// An instance for the specified type. + public IFullLogger GetLogger(Type type) => logger; +} diff --git a/src/tests/ReactiveUI.Test.Utilities/Logging/TestLogger.cs b/src/tests/ReactiveUI.Test.Utilities/Logging/TestLogger.cs new file mode 100644 index 0000000000..8d6b4a5ce8 --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Logging/TestLogger.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2025 .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.Generic; +using System.ComponentModel; +using Splat; + +namespace ReactiveUI.Tests.Utilities.Logging; + +/// +/// Provides a test implementation of the ILogger interface for capturing and inspecting log messages during unit +/// tests. +/// +/// +/// TestLogger records log messages in memory, allowing test code to verify logging behavior without +/// external dependencies. The logger is initialized with an empty message collection and the log level set to Debug. +/// Use the Messages property to access the recorded log entries for assertions or analysis in test scenarios. +/// +public class TestLogger : ILogger +{ + /// + /// Initializes a new instance of the class with default settings. + /// + public TestLogger() + { + Messages = []; + Level = LogLevel.Debug; + } + + /// + /// Gets the collection of log messages recorded by the logger. + /// + /// + /// Each entry in the collection contains the message text, the associated type, and the log + /// level. The collection is read-only; to add messages, use the appropriate logging methods provided by the + /// class. + /// + public List<(string message, Type type, LogLevel logLevel)> Messages { get; } + + /// + public LogLevel Level { get; set; } + + /// + public void Write(Exception exception, string message, Type type, LogLevel logLevel) => + Messages.Add((message, typeof(TestLogger), logLevel)); + + /// + public void Write(string message, LogLevel logLevel) => Messages.Add((message, typeof(TestLogger), logLevel)); + + /// + public void Write(Exception exception, string message, LogLevel logLevel) => + Messages.Add((message, typeof(TestLogger), logLevel)); + + /// + public void Write([Localizable(false)] string message, [Localizable(false)] Type type, LogLevel logLevel) => + Messages.Add((message, type, logLevel)); +} diff --git a/src/tests/ReactiveUI.Test.Utilities/MessageBus/MessageBusTestContextExtensions.cs b/src/tests/ReactiveUI.Test.Utilities/MessageBus/MessageBusTestContextExtensions.cs new file mode 100644 index 0000000000..bc411a889a --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/MessageBus/MessageBusTestContextExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2025 .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.Utilities.MessageBus; + +/// +/// Extensions for accessing message bus from TestContext. +/// +public static class MessageBusTestContextExtensions +{ + /// + /// Gets the message bus configured for this test. + /// + /// The test context. + /// The message bus instance. + public static IMessageBus GetMessageBus(this TestContext context) + { + ArgumentNullException.ThrowIfNull(context); + return (IMessageBus)(context.StateBag.Items["MessageBus"] ?? new ReactiveUI.MessageBus()); + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/MessageBus/WithMessageBusExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/MessageBus/WithMessageBusExecutor.cs new file mode 100644 index 0000000000..38bd1e2f8e --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/MessageBus/WithMessageBusExecutor.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2025 .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.Utilities.MessageBus; + +/// +/// Test executor that sets up an isolated MessageBus for test duration. +/// +public class WithMessageBusExecutor : ITestExecutor +{ + /// + public async ValueTask ExecuteTest(TestContext context, Func testAction) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(testAction); + + var scheduler = ImmediateScheduler.Instance; + var virtualTimeScheduler = new VirtualTimeScheduler(); + var testBus = new ReactiveUI.MessageBus(); + var previousBus = ReactiveUI.MessageBus.Current; + + context.StateBag.Items["Scheduler"] = scheduler; + context.StateBag.Items["VirtualTimeScheduler"] = virtualTimeScheduler; + context.StateBag.Items["MessageBus"] = testBus; + + // Force-reset any previous builder state to avoid waiting deadlocks. + RxAppBuilder.ResetForTesting(); + + _ = RxAppBuilder.CreateReactiveUIBuilder() + .WithMainThreadScheduler(scheduler) + .WithTaskPoolScheduler(scheduler) + .WithMessageBus(testBus) + .WithCoreServices() + .BuildApp(); + + try + { + context.RestoreExecutionContext(); + await testAction(); + } + finally + { + ReactiveUI.MessageBus.Current = previousBus; + RxAppBuilder.ResetForTesting(); + } + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/ReactiveUI.Test.Utilities.csproj b/src/tests/ReactiveUI.Test.Utilities/ReactiveUI.Test.Utilities.csproj new file mode 100644 index 0000000000..11d74d7fca --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/ReactiveUI.Test.Utilities.csproj @@ -0,0 +1,30 @@ + + + $(ReactiveUITestingTargets) + false + false + ReactiveUI.Tests.Utilities + Shared test utilities for ReactiveUI test projects + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tests/ReactiveUI.Test.Utilities/Schedulers/TestContextExtensions.cs b/src/tests/ReactiveUI.Test.Utilities/Schedulers/TestContextExtensions.cs new file mode 100644 index 0000000000..ea262324d6 --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Schedulers/TestContextExtensions.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2025 .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.Utilities.Schedulers; + +/// +/// Extensions for accessing test utilities from TestContext. +/// +public static class TestContextExtensions +{ + /// + /// Gets the scheduler configured for this test (ImmediateScheduler). + /// + /// The test context. + /// The scheduler instance. + public static IScheduler GetScheduler(this TestContext context) + { + ArgumentNullException.ThrowIfNull(context); + return (IScheduler)(context.StateBag.Items["Scheduler"] ?? ImmediateScheduler.Instance); + } + + /// + /// Gets the VirtualTimeScheduler configured for this test. + /// Only available when using WithVirtualTimeSchedulerExecutor. + /// + /// The test context. + /// The VirtualTimeScheduler instance. + /// Thrown when VirtualTimeScheduler is not configured. + public static VirtualTimeScheduler GetVirtualTimeScheduler(this TestContext? context) + { + ArgumentNullException.ThrowIfNull(context); + return (VirtualTimeScheduler)(context.StateBag.Items["VirtualTimeScheduler"] + ?? throw new InvalidOperationException("VirtualTimeScheduler not configured. Use [TestExecutor] on the test method or class.")); + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/Schedulers/VirtualTimeScheduler.cs b/src/tests/ReactiveUI.Test.Utilities/Schedulers/VirtualTimeScheduler.cs new file mode 100644 index 0000000000..d5ecb65fba --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Schedulers/VirtualTimeScheduler.cs @@ -0,0 +1,172 @@ +// Copyright (c) 2025 .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.Generic; +using System.Reactive.Disposables; + +namespace ReactiveUI.Tests.Utilities.Schedulers; + +/// +/// Lightweight virtual time scheduler for testing. +/// Provides deterministic time control without heavyweight dependencies. +/// +public sealed class VirtualTimeScheduler : IScheduler +{ + private readonly SortedList> _scheduledItems = []; + private DateTimeOffset _now = DateTimeOffset.MinValue; + + /// + /// Gets the current virtual time. + /// + public DateTimeOffset Now => _now; + + /// + /// Schedules an action to be executed immediately. + /// + /// The type of state passed to the action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + public IDisposable Schedule(TState state, Func action) + { + ArgumentNullException.ThrowIfNull(action); + return Schedule(state, TimeSpan.Zero, action); + } + + /// + /// Schedules an action to be executed after the specified due time. + /// + /// The type of state passed to the action. + /// State passed to the action to be executed. + /// Relative time after which to execute the action. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + ArgumentNullException.ThrowIfNull(action); + + var scheduledTime = _now.Add(dueTime); + + if (!_scheduledItems.TryGetValue(scheduledTime, out var actions)) + { + actions = []; + _scheduledItems[scheduledTime] = actions; + } + + var cancelled = false; + actions.Add(() => + { + if (!cancelled) + { + action(this, state); + } + }); + + return Disposable.Create(() => cancelled = true); + } + + /// + /// Schedules an action to be executed at the specified due time. + /// + /// The type of state passed to the action. + /// State passed to the action to be executed. + /// Absolute time at which to execute the action. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) + { + ArgumentNullException.ThrowIfNull(action); + + if (!_scheduledItems.TryGetValue(dueTime, out var actions)) + { + actions = []; + _scheduledItems[dueTime] = actions; + } + + var cancelled = false; + actions.Add(() => + { + if (!cancelled) + { + action(this, state); + } + }); + + return Disposable.Create(() => cancelled = true); + } + + /// + /// Advances virtual time by the specified duration, executing all scheduled actions. + /// + /// The time span to advance. + public void AdvanceBy(TimeSpan time) + { + var targetTime = _now.Add(time); + while (_scheduledItems.Count > 0 && _scheduledItems.Keys[0] <= targetTime) + { + var scheduledTime = _scheduledItems.Keys[0]; + var actions = _scheduledItems[scheduledTime]; + _scheduledItems.RemoveAt(0); + + _now = scheduledTime; + + // Execute all actions scheduled for this time + foreach (var action in actions) + { + action(); + } + } + + _now = targetTime; + } + + /// + /// Advances virtual time to the specified absolute time, executing all scheduled actions. + /// + /// The absolute time to advance to. + public void AdvanceTo(DateTimeOffset time) + { + if (time < _now) + { + throw new ArgumentException("Cannot advance to a time in the past", nameof(time)); + } + + while (_scheduledItems.Count > 0 && _scheduledItems.Keys[0] <= time) + { + var scheduledTime = _scheduledItems.Keys[0]; + var actions = _scheduledItems[scheduledTime]; + _scheduledItems.RemoveAt(0); + + _now = scheduledTime; + + foreach (var action in actions) + { + action(); + } + } + + _now = time; + } + + /// + /// Runs all scheduled actions until there are no more. + /// + public void Start() + { + while (_scheduledItems.Count > 0) + { + var scheduledTime = _scheduledItems.Keys[0]; + var actions = _scheduledItems[scheduledTime]; + _scheduledItems.RemoveAt(0); + + _now = scheduledTime; + + foreach (var action in actions) + { + action(); + } + } + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithSchedulerExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithSchedulerExecutor.cs new file mode 100644 index 0000000000..20af9be86e --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithSchedulerExecutor.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2025 .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.Utilities.Schedulers; + +/// +/// Test executor that wraps test execution with ImmediateScheduler override. +/// Sets RxSchedulers.MainThreadScheduler and TaskpoolScheduler for test duration. +/// +public class WithSchedulerExecutor : ITestExecutor +{ + /// + public async ValueTask ExecuteTest(TestContext context, Func testAction) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(testAction); + + var scheduler = ImmediateScheduler.Instance; + IScheduler? originalMainThreadScheduler = null; + IScheduler? originalTaskpoolScheduler = null; + + try + { + // Ensure TestContext.Current is set in case of async flow issues + context.RestoreExecutionContext(); + + originalMainThreadScheduler = RxSchedulers.MainThreadScheduler; + originalTaskpoolScheduler = RxSchedulers.TaskpoolScheduler; + + // Store scheduler in StateBag for retrieval by tests + context.StateBag.Items["Scheduler"] = scheduler; + + RxAppBuilder.CreateReactiveUIBuilder() + .WithMainThreadScheduler(scheduler) + .WithTaskPoolScheduler(scheduler) + .WithCoreServices() + .BuildApp(); + + RxSchedulers.MainThreadScheduler = scheduler; + RxSchedulers.TaskpoolScheduler = scheduler; + + await testAction(); + } + finally + { + if (originalMainThreadScheduler is not null) + { + RxSchedulers.MainThreadScheduler = originalMainThreadScheduler; + } + + if (originalTaskpoolScheduler is not null) + { + RxSchedulers.TaskpoolScheduler = originalTaskpoolScheduler; + } + + RxAppBuilder.ResetForTesting(); + } + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithVirtualTimeSchedulerExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithVirtualTimeSchedulerExecutor.cs new file mode 100644 index 0000000000..3f8a4f07a5 --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithVirtualTimeSchedulerExecutor.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2025 .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.Utilities.Schedulers; + +/// +/// Test executor that wraps test execution with VirtualTimeScheduler. +/// Provides deterministic time control for testing time-dependent behavior. +/// +public class WithVirtualTimeSchedulerExecutor : ITestExecutor +{ + /// + public async ValueTask ExecuteTest(TestContext context, Func testAction) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(testAction); + + IScheduler? originalMainThreadScheduler = null; + IScheduler? originalTaskpoolScheduler = null; + + try + { + // Ensure TestContext.Current is set in case of async flow issues + context.RestoreExecutionContext(); + + var scheduler = new VirtualTimeScheduler(); + originalMainThreadScheduler = RxSchedulers.MainThreadScheduler; + originalTaskpoolScheduler = RxSchedulers.TaskpoolScheduler; + + // Store both in StateBag for retrieval by tests + context.StateBag.Items["VirtualTimeScheduler"] = scheduler; + context.StateBag.Items["Scheduler"] = scheduler; + + RxAppBuilder.CreateReactiveUIBuilder() + .WithMainThreadScheduler(scheduler) + .WithTaskPoolScheduler(scheduler) + .WithCoreServices() + .BuildApp(); + + RxSchedulers.MainThreadScheduler = scheduler; + RxSchedulers.TaskpoolScheduler = scheduler; + + await testAction(); + } + finally + { + if (originalMainThreadScheduler is not null) + { + RxSchedulers.MainThreadScheduler = originalMainThreadScheduler; + } + + if (originalTaskpoolScheduler is not null) + { + RxSchedulers.TaskpoolScheduler = originalTaskpoolScheduler; + } + + RxAppBuilder.ResetForTesting(); + } + } +} diff --git a/src/tests/ReactiveUI.Test.Utilities/Sequencing/TestSequencer.cs b/src/tests/ReactiveUI.Test.Utilities/Sequencing/TestSequencer.cs new file mode 100644 index 0000000000..a539582f0f --- /dev/null +++ b/src/tests/ReactiveUI.Test.Utilities/Sequencing/TestSequencer.cs @@ -0,0 +1,88 @@ +// Copyright (c) 2025 .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.Utilities.Sequencing; + +/// +/// Test Sequencer for coordinating async test phases. +/// +/// +public class TestSequencer : IDisposable +{ + private readonly Barrier _phaseSync; + private bool _disposedValue; + + /// + /// Initializes a new instance of the class. + /// + public TestSequencer() => _phaseSync = new Barrier(2); + + /// + /// Gets the number of completed phases. + /// + /// + /// The completed phases. + /// + public long CompletedPhases => _phaseSync.CurrentPhaseNumber; + + /// + /// Gets the current phase. + /// + /// + /// The current phase. + /// + public long CurrentPhase { get; private set; } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Advances this phase instance. + /// + /// The comment for Test visual identification Purposes only. + /// + /// A representing the asynchronous operation. + /// + public async Task AdvancePhaseAsync(string comment = "") + { + if (_phaseSync.ParticipantCount == _phaseSync.ParticipantsRemaining) + { + CurrentPhase = CompletedPhases + 1; + } + + // Synchronize both participants and then yield once to allow post-barrier continuations + // to run before returning to the caller. This reduces timing-related flakiness in tests + // that assert immediately after advancing a phase. + await Task.Run(() => _phaseSync.SignalAndWait(CancellationToken.None)).ConfigureAwait(false); + await Task.Yield(); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// + /// true to release both managed and unmanaged resources; false to release only + /// unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _phaseSync.Dispose(); + } + + _disposedValue = true; + } + } +} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/common-gui/Mocks/RaceConditionFixture.cs b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionFixture.cs similarity index 96% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/common-gui/Mocks/RaceConditionFixture.cs rename to src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionFixture.cs index bbaa1fff5f..dd58952ecc 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/common-gui/Mocks/RaceConditionFixture.cs +++ b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionFixture.cs @@ -3,7 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.TestGuiMocks.CommonGuiMocks.Mocks; /// /// A fixture for demonstrating race conditions. diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/common-gui/Mocks/RaceConditionNameOfFixture.cs b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionNameOfFixture.cs similarity index 96% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/common-gui/Mocks/RaceConditionNameOfFixture.cs rename to src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionNameOfFixture.cs index e6bb311ac4..490a40807e 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/common-gui/Mocks/RaceConditionNameOfFixture.cs +++ b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/RaceConditionNameOfFixture.cs @@ -3,7 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.TestGuiMocks.CommonGuiMocks.Mocks; /// /// A fixture for RaceCondition and NameOf. diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/windows-xaml/Mocks/TestScreen.cs b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/TestScreen.cs similarity index 90% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/windows-xaml/Mocks/TestScreen.cs rename to src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/TestScreen.cs index 21cafdee6b..7197f57e79 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/windows-xaml/Mocks/TestScreen.cs +++ b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/Mocks/TestScreen.cs @@ -3,7 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.TestGuiMocks.CommonGuiMocks.Mocks; public class TestScreen : ReactiveObject, IScreen { diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/common-gui/ProductionMode.cs b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/ProductionMode.cs similarity index 92% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/common-gui/ProductionMode.cs rename to src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/ProductionMode.cs index 065d21100e..79b34c0ee0 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/common-gui/ProductionMode.cs +++ b/src/tests/ReactiveUI.TestGuiMocks/CommonGuiMocks/ProductionMode.cs @@ -3,12 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.TestGuiMocks.CommonGuiMocks; /// /// Detects if we are in production mode or not. /// -internal class ProductionMode : IModeDetector, IPlatformModeDetector +public class ProductionMode : IModeDetector, IPlatformModeDetector { private static readonly ProductionMode Instance = new(); diff --git a/src/tests/ReactiveUI.TestGuiMocks/ReactiveUI.TestGuiMocks.csproj b/src/tests/ReactiveUI.TestGuiMocks/ReactiveUI.TestGuiMocks.csproj new file mode 100644 index 0000000000..926924f156 --- /dev/null +++ b/src/tests/ReactiveUI.TestGuiMocks/ReactiveUI.TestGuiMocks.csproj @@ -0,0 +1,39 @@ + + + + $(ReactiveUITestingUITargets) + $(NoWarn);CS1591 + false + Shared GUI test mocks for ReactiveUI tests + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.Testing.DotNet10_0.verified.txt b/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.Testing.DotNet10_0.verified.txt new file mode 100644 index 0000000000..680aa0959f --- /dev/null +++ b/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.Testing.DotNet10_0.verified.txt @@ -0,0 +1,57 @@ +namespace ReactiveUI.Testing +{ + public abstract class AppBuilderTestBase + { + protected AppBuilderTestBase() { } + protected static System.Threading.Tasks.Task RunAppBuilderTestAsync(System.Action testBody) { } + protected static System.Threading.Tasks.Task RunAppBuilderTestAsync(System.Func testBody) { } + } + public interface IBuilder { } + public static class IBuilderExtensions + { + public static TBuilder With(this TBuilder builder, ref System.Collections.Generic.Dictionary dictionary, System.Collections.Generic.IDictionary keyValuePair) + where TKey : notnull { } + extension(TBuilder builder) + where TBuilder : notnull, ReactiveUI.Testing.IBuilder + { + public TBuilder With(out TField field, TField value) { } + public TBuilder With(ref System.Collections.Generic.List? field, System.Collections.Generic.IEnumerable values) { } + public TBuilder With(ref System.Collections.Generic.List? field, TField value) { } + public TBuilder With(ref System.Collections.Generic.Dictionary dictionary, System.Collections.Generic.KeyValuePair keyValuePair) + where TKey : notnull { } + public TBuilder With(ref System.Collections.Generic.Dictionary dictionary, TKey key, TField value) + where TKey : notnull { } + } + } + public static class MessageBusExtensions + { + public static void With(this ReactiveUI.IMessageBus messageBus, System.Action block) { } + public static TRet With(this ReactiveUI.IMessageBus messageBus, System.Func block) { } + public static System.IDisposable WithMessageBus(this ReactiveUI.IMessageBus messageBus) { } + } + public static class RxTest + { + public static System.Threading.Tasks.Task AppBuilderTestAsync(System.Func testBody, int maxWaitMs = 60000) { } + } + public static class SchedulerExtensions + { + public static void With(this T scheduler, System.Action block) + where T : System.Reactive.Concurrency.IScheduler { } + public static TRet With(this T scheduler, System.Func block) + where T : System.Reactive.Concurrency.IScheduler { } + public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func block) + where T : System.Reactive.Concurrency.IScheduler { } + public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func> block) + where T : System.Reactive.Concurrency.IScheduler { } + public static System.IDisposable WithScheduler(System.Reactive.Concurrency.IScheduler scheduler) { } + } + public class TestSequencer : System.IDisposable + { + public TestSequencer() { } + public long CompletedPhases { get; } + public long CurrentPhase { get; } + public System.Threading.Tasks.Task AdvancePhaseAsync(string comment = "") { } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + } +} diff --git a/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.Testing.DotNet8_0.verified.txt b/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.Testing.DotNet8_0.verified.txt new file mode 100644 index 0000000000..680aa0959f --- /dev/null +++ b/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.Testing.DotNet8_0.verified.txt @@ -0,0 +1,57 @@ +namespace ReactiveUI.Testing +{ + public abstract class AppBuilderTestBase + { + protected AppBuilderTestBase() { } + protected static System.Threading.Tasks.Task RunAppBuilderTestAsync(System.Action testBody) { } + protected static System.Threading.Tasks.Task RunAppBuilderTestAsync(System.Func testBody) { } + } + public interface IBuilder { } + public static class IBuilderExtensions + { + public static TBuilder With(this TBuilder builder, ref System.Collections.Generic.Dictionary dictionary, System.Collections.Generic.IDictionary keyValuePair) + where TKey : notnull { } + extension(TBuilder builder) + where TBuilder : notnull, ReactiveUI.Testing.IBuilder + { + public TBuilder With(out TField field, TField value) { } + public TBuilder With(ref System.Collections.Generic.List? field, System.Collections.Generic.IEnumerable values) { } + public TBuilder With(ref System.Collections.Generic.List? field, TField value) { } + public TBuilder With(ref System.Collections.Generic.Dictionary dictionary, System.Collections.Generic.KeyValuePair keyValuePair) + where TKey : notnull { } + public TBuilder With(ref System.Collections.Generic.Dictionary dictionary, TKey key, TField value) + where TKey : notnull { } + } + } + public static class MessageBusExtensions + { + public static void With(this ReactiveUI.IMessageBus messageBus, System.Action block) { } + public static TRet With(this ReactiveUI.IMessageBus messageBus, System.Func block) { } + public static System.IDisposable WithMessageBus(this ReactiveUI.IMessageBus messageBus) { } + } + public static class RxTest + { + public static System.Threading.Tasks.Task AppBuilderTestAsync(System.Func testBody, int maxWaitMs = 60000) { } + } + public static class SchedulerExtensions + { + public static void With(this T scheduler, System.Action block) + where T : System.Reactive.Concurrency.IScheduler { } + public static TRet With(this T scheduler, System.Func block) + where T : System.Reactive.Concurrency.IScheduler { } + public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func block) + where T : System.Reactive.Concurrency.IScheduler { } + public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func> block) + where T : System.Reactive.Concurrency.IScheduler { } + public static System.IDisposable WithScheduler(System.Reactive.Concurrency.IScheduler scheduler) { } + } + public class TestSequencer : System.IDisposable + { + public TestSequencer() { } + public long CompletedPhases { get; } + public long CurrentPhase { get; } + public System.Threading.Tasks.Task AdvancePhaseAsync(string comment = "") { } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + } +} diff --git a/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.Testing.DotNet9_0.verified.txt b/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.Testing.DotNet9_0.verified.txt new file mode 100644 index 0000000000..680aa0959f --- /dev/null +++ b/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.Testing.DotNet9_0.verified.txt @@ -0,0 +1,57 @@ +namespace ReactiveUI.Testing +{ + public abstract class AppBuilderTestBase + { + protected AppBuilderTestBase() { } + protected static System.Threading.Tasks.Task RunAppBuilderTestAsync(System.Action testBody) { } + protected static System.Threading.Tasks.Task RunAppBuilderTestAsync(System.Func testBody) { } + } + public interface IBuilder { } + public static class IBuilderExtensions + { + public static TBuilder With(this TBuilder builder, ref System.Collections.Generic.Dictionary dictionary, System.Collections.Generic.IDictionary keyValuePair) + where TKey : notnull { } + extension(TBuilder builder) + where TBuilder : notnull, ReactiveUI.Testing.IBuilder + { + public TBuilder With(out TField field, TField value) { } + public TBuilder With(ref System.Collections.Generic.List? field, System.Collections.Generic.IEnumerable values) { } + public TBuilder With(ref System.Collections.Generic.List? field, TField value) { } + public TBuilder With(ref System.Collections.Generic.Dictionary dictionary, System.Collections.Generic.KeyValuePair keyValuePair) + where TKey : notnull { } + public TBuilder With(ref System.Collections.Generic.Dictionary dictionary, TKey key, TField value) + where TKey : notnull { } + } + } + public static class MessageBusExtensions + { + public static void With(this ReactiveUI.IMessageBus messageBus, System.Action block) { } + public static TRet With(this ReactiveUI.IMessageBus messageBus, System.Func block) { } + public static System.IDisposable WithMessageBus(this ReactiveUI.IMessageBus messageBus) { } + } + public static class RxTest + { + public static System.Threading.Tasks.Task AppBuilderTestAsync(System.Func testBody, int maxWaitMs = 60000) { } + } + public static class SchedulerExtensions + { + public static void With(this T scheduler, System.Action block) + where T : System.Reactive.Concurrency.IScheduler { } + public static TRet With(this T scheduler, System.Func block) + where T : System.Reactive.Concurrency.IScheduler { } + public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func block) + where T : System.Reactive.Concurrency.IScheduler { } + public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func> block) + where T : System.Reactive.Concurrency.IScheduler { } + public static System.IDisposable WithScheduler(System.Reactive.Concurrency.IScheduler scheduler) { } + } + public class TestSequencer : System.IDisposable + { + public TestSequencer() { } + public long CompletedPhases { get; } + public long CurrentPhase { get; } + public System.Threading.Tasks.Task AdvancePhaseAsync(string comment = "") { } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + } +} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.cs b/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.cs similarity index 50% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.cs rename to src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.cs index 105268556d..564d4bab19 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.cs +++ b/src/tests/ReactiveUI.Testing.Tests/API/ApiApprovalTests.cs @@ -3,18 +3,23 @@ // 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; +using ReactiveUI.Tests.Utilities; +using TUnit.Core; +using TUnit.Core.Enums; + +namespace ReactiveUI.Testing.Tests.API; /// -/// Checks the WinForms API to make sure there aren't any unexpected public API changes. +/// Checks to make sure that the API is consistent with previous releases, and new API changes are highlighted. /// [ExcludeFromCodeCoverage] -public class WinformsApiApprovalTests +[RunOn(OS.Windows)] +public class ApiApprovalTests { /// - /// Checks the approved vs the received API. + /// Generates public API for the ReactiveUI.Testing API. /// /// A task to monitor the process. [Test] - public Task Winforms() => typeof(ReactiveUI.Winforms.WinformsCreatesObservableForProperty).Assembly.CheckApproval(["ReactiveUI"]); + public Task Testing() => typeof(Testing.SchedulerExtensions).Assembly.CheckApproval(["ReactiveUI"]); } diff --git a/src/tests/ReactiveUI.Testing.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Testing.Tests/AssemblyHooks.cs index 809654d116..b81e1fc2ed 100644 --- a/src/tests/ReactiveUI.Testing.Tests/AssemblyHooks.cs +++ b/src/tests/ReactiveUI.Testing.Tests/AssemblyHooks.cs @@ -4,6 +4,7 @@ // See the LICENSE file in the project root for full license information. using System; +using ReactiveUI.Builder; using TUnit.Core; namespace ReactiveUI.Testing.Tests; @@ -21,6 +22,11 @@ public static void AssemblySetup() { // Override ModeDetector to ensure we're detected as being in a unit test runner ModeDetector.OverrideModeDetector(new TestModeDetector()); + + // Initialize ReactiveUI with core services + RxAppBuilder.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); } /// diff --git a/src/tests/ReactiveUI.Testing.Tests/ReactiveUI.Testing.Tests.csproj b/src/tests/ReactiveUI.Testing.Tests/ReactiveUI.Testing.Tests.csproj index c6a3e8b715..df64b1c93d 100644 --- a/src/tests/ReactiveUI.Testing.Tests/ReactiveUI.Testing.Tests.csproj +++ b/src/tests/ReactiveUI.Testing.Tests/ReactiveUI.Testing.Tests.csproj @@ -7,7 +7,9 @@ + + @@ -15,12 +17,15 @@ + + + diff --git a/src/tests/ReactiveUI.Testing.Tests/SchedulerExtensionTests.cs b/src/tests/ReactiveUI.Testing.Tests/SchedulerExtensionTests.cs index 86cddf0d52..86f3209b79 100644 --- a/src/tests/ReactiveUI.Testing.Tests/SchedulerExtensionTests.cs +++ b/src/tests/ReactiveUI.Testing.Tests/SchedulerExtensionTests.cs @@ -4,13 +4,20 @@ // See the LICENSE file in the project root for full license information. using System.Threading; + using Microsoft.Reactive.Testing; +using ReactiveUI.Testing.Reactive; + +using TUnit.Core.Executors; + namespace ReactiveUI.Testing.Tests; /// /// Tests for SchedulerExtensions. /// +[NotInParallel] +[TestExecutor] public sealed class SchedulerExtensionTests { /// @@ -21,8 +28,8 @@ public sealed class SchedulerExtensionTests public async Task WithScheduler_ShouldSetBothRxAppAndRxSchedulersSchedulers() { var testScheduler = new TestScheduler(); - var originalMainThread = RxApp.MainThreadScheduler; - var originalTaskpool = RxApp.TaskpoolScheduler; + var originalMainThread = RxSchedulers.MainThreadScheduler; + var originalTaskpool = RxSchedulers.TaskpoolScheduler; using (SchedulerExtensions.WithScheduler(testScheduler)) { @@ -30,16 +37,16 @@ public async Task WithScheduler_ShouldSetBothRxAppAndRxSchedulersSchedulers() using (Assert.Multiple()) { // Verify RxApp schedulers are set - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(testScheduler); - await Assert.That(RxApp.TaskpoolScheduler).IsEqualTo(testScheduler); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(testScheduler); + await Assert.That(RxSchedulers.TaskpoolScheduler).IsEqualTo(testScheduler); } } // Verify schedulers are restored after disposal using (Assert.Multiple()) { - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(originalMainThread); - await Assert.That(RxApp.TaskpoolScheduler).IsEqualTo(originalTaskpool); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(originalMainThread); + await Assert.That(RxSchedulers.TaskpoolScheduler).IsEqualTo(originalTaskpool); } } @@ -52,23 +59,23 @@ public async Task WithScheduler_NestedCalls_ShouldWorkSequentially() { var scheduler1 = new TestScheduler(); var scheduler2 = new TestScheduler(); - var originalMainThread = RxApp.MainThreadScheduler; + var originalMainThread = RxSchedulers.MainThreadScheduler; using (SchedulerExtensions.WithScheduler(scheduler1)) { - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(scheduler1); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(scheduler1); using (SchedulerExtensions.WithScheduler(scheduler2)) { - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(scheduler2); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(scheduler2); } // After inner scope, should restore to scheduler1 - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(scheduler1); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(scheduler1); } // After outer scope, should restore to original - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(originalMainThread); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(originalMainThread); } /// @@ -97,75 +104,8 @@ public async Task WithScheduler_ExceptionInCriticalSection_ShouldStillReleaseGat // Second call should succeed (gate was released despite exception) using (SchedulerExtensions.WithScheduler(scheduler2)) { - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(scheduler2); - } - } - - /// - /// Tests concurrent access to WithScheduler from multiple threads. - /// - /// A representing the asynchronous operation. - [Test] - public async Task WithScheduler_ConcurrentAccess_ShouldSerialize() - { - const int threadCount = 5; - const int iterationsPerThread = 3; - var exceptions = new List(); - var tasks = new List(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); - - // Use CountdownEvent instead of Barrier to avoid potential deadlocks - using var startSignal = new CountdownEvent(1); - - for (var i = 0; i < threadCount; i++) - { - var threadId = i; - tasks.Add(Task.Run( - () => - { - try - { - // Wait for start signal - startSignal.Wait(cts.Token); - - for (var j = 0; j < iterationsPerThread; j++) - { - var scheduler = new TestScheduler(); - using (SchedulerExtensions.WithScheduler(scheduler)) - { - // Verify scheduler is set - if (RxApp.MainThreadScheduler != scheduler) - { - throw new InvalidOperationException($"Thread {threadId}: Scheduler mismatch!"); - } - - // Simulate minimal work - Thread.SpinWait(100); - } - } - } - catch (Exception ex) - { - lock (exceptions) - { - exceptions.Add(ex); - } - } - }, - cts.Token)); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(scheduler2); } - - // Give tasks a moment to start and wait at the signal - await Task.Delay(50, cts.Token); - - // Release all threads simultaneously - startSignal.Signal(); - - // Wait for all tasks with timeout - await Task.WhenAll(tasks); - - // Verify no exceptions occurred - await Assert.That(exceptions).IsEmpty(); } /// @@ -176,12 +116,12 @@ public async Task WithScheduler_ConcurrentAccess_ShouldSerialize() public async Task With_Function_ShouldExecuteAndReturnValue() { var scheduler = new TestScheduler(); - var originalScheduler = RxApp.MainThreadScheduler; + var originalScheduler = RxSchedulers.MainThreadScheduler; var result = scheduler.With(s => { // Inside the block, scheduler should be active - if (RxApp.MainThreadScheduler != s) + if (RxSchedulers.MainThreadScheduler != s) { throw new InvalidOperationException("Scheduler not set correctly"); } @@ -192,7 +132,7 @@ public async Task With_Function_ShouldExecuteAndReturnValue() await Assert.That(result).IsEqualTo(42); // After the block, original scheduler should be restored - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(originalScheduler); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(originalScheduler); } /// @@ -203,7 +143,7 @@ public async Task With_Function_ShouldExecuteAndReturnValue() public async Task With_Action_ShouldExecute() { var scheduler = new TestScheduler(); - var originalScheduler = RxApp.MainThreadScheduler; + var originalScheduler = RxSchedulers.MainThreadScheduler; var executed = false; scheduler.With(s => @@ -211,14 +151,14 @@ public async Task With_Action_ShouldExecute() executed = true; // Inside the block, scheduler should be active - if (RxApp.MainThreadScheduler != s) + if (RxSchedulers.MainThreadScheduler != s) { throw new InvalidOperationException("Scheduler not set correctly"); } }); await Assert.That(executed).IsTrue(); - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(originalScheduler); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(originalScheduler); } /// @@ -229,12 +169,12 @@ public async Task With_Action_ShouldExecute() public async Task WithAsync_Function_ShouldExecuteAndReturnValue() { var scheduler = new TestScheduler(); - var originalScheduler = RxApp.MainThreadScheduler; + var originalScheduler = RxSchedulers.MainThreadScheduler; var result = await scheduler.WithAsync(s => { // Inside the block, scheduler should be active - if (RxApp.MainThreadScheduler != s) + if (RxSchedulers.MainThreadScheduler != s) { throw new InvalidOperationException("Scheduler not set correctly"); } @@ -243,7 +183,7 @@ public async Task WithAsync_Function_ShouldExecuteAndReturnValue() }); await Assert.That(result).IsEqualTo(42); - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(originalScheduler); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(originalScheduler); } /// @@ -254,7 +194,7 @@ public async Task WithAsync_Function_ShouldExecuteAndReturnValue() public async Task WithAsync_Action_ShouldExecute() { var scheduler = new TestScheduler(); - var originalScheduler = RxApp.MainThreadScheduler; + var originalScheduler = RxSchedulers.MainThreadScheduler; var executed = false; await scheduler.WithAsync(s => @@ -262,7 +202,7 @@ await scheduler.WithAsync(s => executed = true; // Inside the block, scheduler should be active - if (RxApp.MainThreadScheduler != s) + if (RxSchedulers.MainThreadScheduler != s) { throw new InvalidOperationException("Scheduler not set correctly"); } @@ -271,7 +211,7 @@ await scheduler.WithAsync(s => }); await Assert.That(executed).IsTrue(); - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(originalScheduler); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(originalScheduler); } /// @@ -288,7 +228,7 @@ public async Task WithScheduler_RapidSequentialCalls_ShouldWork() var scheduler = new TestScheduler(); using (SchedulerExtensions.WithScheduler(scheduler)) { - await Assert.That(RxApp.MainThreadScheduler).IsEqualTo(scheduler); + await Assert.That(RxSchedulers.MainThreadScheduler).IsEqualTo(scheduler); } } diff --git a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet10_0.verified.txt b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet10_0.verified.txt index a45b0b4dfd..27c8f9b9a5 100644 --- a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet10_0.verified.txt +++ b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet10_0.verified.txt @@ -7,18 +7,22 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Builder.WpfApp")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Drawing")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Maui")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Maui.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Test.Utilities")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.TestGuiMocks")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Testing")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Testing.Reactive")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Uno")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Uno.WinUI")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.WinForms.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.WinUI")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Winforms")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Wpf")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Wpf.Tests")] namespace ReactiveUI { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static class AutoPersistHelper { public static System.IDisposable ActOnEveryObject(this System.Collections.ObjectModel.ObservableCollection @this, System.Action onAdd, System.Action onRemove) @@ -30,19 +34,69 @@ namespace ReactiveUI public static System.IDisposable ActOnEveryObject(this TCollection collection, System.Action onAdd, System.Action onRemove) where TItem : ReactiveUI.IReactiveObject where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } - public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, System.TimeSpan? interval = default) + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + public static System.IDisposable AutoPersist<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(this T @this, System.Func> doPersist, System.TimeSpan? interval = default) where T : ReactiveUI.IReactiveObject { } - public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) + public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) where T : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + public static System.IDisposable AutoPersist<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TDontCare>(this T @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) + where T : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where T : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ReadOnlyObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ReadOnlyObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this TCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IDisposable AutoPersistCollection(this TCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IDisposable AutoPersistCollection(this TCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.Func metadataProvider, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static ReactiveUI.AutoPersistHelper.AutoPersistMetadata CreateMetadata<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] T>() + where T : ReactiveUI.IReactiveObject { } + public static System.Func CreateMetadataProvider<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] TItem>() + where TItem : ReactiveUI.IReactiveObject { } + public sealed class AutoPersistMetadata + { + public AutoPersistMetadata(bool hasDataContract, System.Collections.Generic.ISet persistablePropertyNames) { } + public bool HasDataContract { get; } + public System.Collections.Generic.ISet PersistablePropertyNames { get; } + } } public enum BindingDirection { @@ -50,17 +104,53 @@ namespace ReactiveUI TwoWay = 1, AsyncOneWay = 2, } - public class ByteToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class BindingFallbackConverterRegistry + { + public BindingFallbackConverterRegistry() { } + public System.Collections.Generic.IEnumerable GetAllConverters() { } + public void Register(ReactiveUI.IBindingFallbackConverter converter) { } + public ReactiveUI.IBindingFallbackConverter? TryGetConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType) { } + } + public sealed class BindingTypeConverterRegistry + { + public BindingTypeConverterRegistry() { } + public System.Collections.Generic.IEnumerable GetAllConverters() { } + public void Register(ReactiveUI.IBindingTypeConverter converter) { } + public ReactiveUI.IBindingTypeConverter? TryGetConverter(System.Type fromType, System.Type toType) { } + } + public abstract class BindingTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + protected BindingTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public abstract int GetAffinityForObjects(); + public abstract bool TryConvert(TFrom? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out TTo? result); + public bool TryConvertTyped(object? from, object? conversionHint, out object? result) { } + } + public sealed class BooleanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public BooleanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(bool from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class ByteToNullableByteTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public ByteToNullableByteTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(byte from, object? conversionHint, out byte? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class ByteToStringTypeConverter : ReactiveUI.BindingTypeConverter { public ByteToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(byte from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public class CanActivateViewFetcher : ReactiveUI.IActivationForViewFetcher { public CanActivateViewFetcher() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } public int GetAffinityForView(System.Type view) { } } @@ -73,10 +163,6 @@ namespace ReactiveUI } public class CombinedReactiveCommand : ReactiveUI.ReactiveCommandBase> { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("CombinedReactiveCommand uses RxApp and ReactiveCommand which require dynamic code" + - " generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CombinedReactiveCommand uses RxApp and ReactiveCommand which may require unrefere" + - "nced code.")] protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable> childCommands, System.IObservable? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } public override System.IObservable CanExecute { get; } public override System.IObservable IsExecuting { get; } @@ -88,40 +174,40 @@ namespace ReactiveUI } public static class CommandBinder { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, string? toEvent = null) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) + where TProp : System.Windows.Input.ICommand + where TControl : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) + where TProp : System.Windows.Input.ICommand + where TControl : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } + where TProp : System.Windows.Input.ICommand + where TControl : class { } } public class CommandBinderImplementation : Splat.IEnableLogger { public CommandBinderImplementation() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) + where TProp : System.Windows.Input.ICommand + where TControl : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } + where TProp : System.Windows.Input.ICommand + where TControl : class { } } public static class ComparerChainingExtensions { @@ -130,98 +216,156 @@ namespace ReactiveUI public static System.Collections.Generic.IComparer ThenByDescending(this System.Collections.Generic.IComparer? parent, System.Func selector) { } public static System.Collections.Generic.IComparer ThenByDescending(this System.Collections.Generic.IComparer? parent, System.Func selector, System.Collections.Generic.IComparer comparer) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Component model type conversion uses reflection and dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Component model type conversion may reference types that could be trimmed")] - public class ComponentModelTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class ComponentModelFallbackConverter : ReactiveUI.IBindingFallbackConverter, Splat.IEnableLogger + { + public ComponentModelFallbackConverter() { } + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification="The callers of this method ensure getting the converter is trim compatible - i.e." + + " the type is not Nullable.")] + public int GetAffinityForObjects([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType) { } + public bool TryConvert([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, object from, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public static class ConverterMigrationHelper { - public ComponentModelTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result) { } + [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { + "TypedConverters", + "FallbackConverters", + "SetMethodConverters"})] + public static System.ValueTuple, System.Collections.Generic.IList, System.Collections.Generic.IList> ExtractConverters(Splat.IReadonlyDependencyResolver resolver) { } + public static void ImportFrom(this ReactiveUI.ConverterService converterService, Splat.IReadonlyDependencyResolver resolver) { } + } + public sealed class ConverterService + { + public ConverterService() { } + public ReactiveUI.BindingFallbackConverterRegistry FallbackConverters { get; } + public ReactiveUI.SetMethodBindingConverterRegistry SetMethodConverters { get; } + public ReactiveUI.BindingTypeConverterRegistry TypedConverters { get; } + public object? ResolveConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType) { } + public ReactiveUI.ISetMethodBindingConverter? ResolveSetMethodConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? toType) { } } - public class CreatesCommandBindingViaCommandParameter : ReactiveUI.ICreatesCommandBinding + public sealed class CreatesCommandBindingViaCommandParameter : ReactiveUI.ICreatesCommandBinding { public CreatesCommandBindingViaCommandParameter() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindCommandToObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindCommandToObject uses methods that may require unreferenced code")] - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindCommandToObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindCommandToObject uses methods that may require unreferenced code")] - public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Property access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Property access may reference members that could be trimmed")] - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs { } public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } } - public class CreatesCommandBindingViaEvent : ReactiveUI.ICreatesCommandBinding + public sealed class CreatesCommandBindingViaEvent : ReactiveUI.ICreatesCommandBinding { public CreatesCommandBindingViaEvent() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Event binding requires dynamic code generation and reflection")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Event binding requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] - public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Event binding requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class { } + public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action addHandler, System.Action removeHandler) + where T : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs { } public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } } - public class DecimalToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class DateOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public DateOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateOnly from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class DateTimeOffsetToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public DateTimeOffsetToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTimeOffset from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class DateTimeToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public DateTimeToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTime from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class DecimalToNullableDecimalTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public DecimalToNullableDecimalTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(decimal from, object? conversionHint, out decimal? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class DecimalToStringTypeConverter : ReactiveUI.BindingTypeConverter { public DecimalToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(decimal from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public sealed class DefaultViewLocator : ReactiveUI.IViewLocator, Splat.IEnableLogger { - public System.Func ViewModelToViewFunc { get; set; } - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification="Mapping does not use dynamic code")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification="Mapping does not use reflection")] public ReactiveUI.DefaultViewLocator Map(System.Func factory, string? contract = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("View resolution uses reflection and type discovery")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("View resolution may reference types that could be trimmed")] - public ReactiveUI.IViewFor? ResolveView(T? viewModel, string? contract = null) { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMe" + + "mbersAttribute, or generic constraints), trimming can\'t validate that the requir" + + "ements of those annotations are met.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to determine the view model type at runtime, which ma" + + "y be incompatible with trimming.")] + public ReactiveUI.IViewFor? ResolveView(object? instance, string? contract = null) { } + public ReactiveUI.IViewFor? ResolveView(string? contract = null) + where TViewModel : class { } public ReactiveUI.DefaultViewLocator Unmap(string? contract = null) where TViewModel : class { } } public static class DependencyResolverMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InitializeReactiveUI uses reflection to locate types which may be trimmed.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InitializeReactiveUI uses reflection to locate types which may be trimmed.")] - public static void InitializeReactiveUI(this Splat.IMutableDependencyResolver resolver, params ReactiveUI.RegistrationNamespace[] registrationNamespaces) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RegisterViewsForViewModels scans the provided assembly and creates instances via " + - "reflection; this is not compatible with AOT.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RegisterViewsForViewModels uses reflection over types and members which may be tr" + - "immed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to register views explicitly.")] public static void RegisterViewsForViewModels(this Splat.IMutableDependencyResolver resolver, System.Reflection.Assembly assembly) { } } - public class DoubleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class DoubleToNullableDoubleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public DoubleToNullableDoubleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(double from, object? conversionHint, out double? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class DoubleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public DoubleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(double from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } - public class DummySuspensionDriver : ReactiveUI.ISuspensionDriver + public sealed class DummySuspensionDriver : ReactiveUI.ISuspensionDriver { public DummySuspensionDriver() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvalidateState uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvalidateState uses methods that may require unreferenced code")] public System.IObservable InvalidateState() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("LoadState uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("LoadState uses methods that may require unreferenced code")] - public System.IObservable LoadState() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SaveState uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SaveState uses methods that may require unreferenced code")] - public System.IObservable SaveState(object state) { } - } - public class EqualityTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] + public System.IObservable LoadState() { } + public System.IObservable LoadState(System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo) { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + public System.IObservable SaveState(T state) { } + public System.IObservable SaveState(T state, System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo) { } + } + public sealed class EqualityTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger { public EqualityTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result) { } - public static object? DoReferenceCast(object? from, System.Type targetType) { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=false, Inherited=false)] public sealed class ExcludeFromViewRegistrationAttribute : System.Attribute @@ -231,16 +375,16 @@ namespace ReactiveUI public static class ExpressionMixins { public static object?[]? GetArgumentsArray(this System.Linq.Expressions.Expression expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression chain analysis requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression chain analysis may reference members that could be trimmed")] public static System.Collections.Generic.IEnumerable GetExpressionChain(this System.Linq.Expressions.Expression expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member info access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member info access may reference members that could be trimmed")] public static System.Reflection.MemberInfo? GetMemberInfo(this System.Linq.Expressions.Expression expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression analysis requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression analysis may reference members that could be trimmed")] public static System.Linq.Expressions.Expression? GetParent(this System.Linq.Expressions.Expression expression) { } } + public sealed class GuidToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public GuidToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.Guid from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } public interface IActivatableView { } public interface IActivatableViewModel { @@ -248,15 +392,24 @@ namespace ReactiveUI } public interface IActivationForViewFetcher { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] System.IObservable GetActivationForView(ReactiveUI.IActivatableView view); int GetAffinityForView(System.Type view); } + public interface IBindingFallbackConverter : Splat.IEnableLogger + { + int GetAffinityForObjects([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType); + bool TryConvert([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, object from, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result); + } public interface IBindingTypeConverter : Splat.IEnableLogger { - int GetAffinityForObjects(System.Type fromType, System.Type toType); - bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result); + System.Type FromType { get; } + System.Type ToType { get; } + int GetAffinityForObjects(); + bool TryConvertTyped(object? from, object? conversionHint, out object? result); + } + public interface IBindingTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + bool TryConvert(TFrom? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out TTo? result); } public interface ICanActivate { @@ -272,24 +425,22 @@ namespace ReactiveUI } public interface ICreatesCommandBinding { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] - System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] - System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] - int GetAffinityForObject(System.Type type, bool hasEventTarget); + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class; + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class; + System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs; int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget); } public interface ICreatesObservableForProperty : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false); } public interface IHandleObservableErrors @@ -298,13 +449,11 @@ namespace ReactiveUI } public interface IInteractionBinderImplementation : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression evaluation which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression evaluation which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.Threading.Tasks.Task> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression evaluation which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression evaluation which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.IObservable> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor; @@ -334,11 +483,9 @@ namespace ReactiveUI public class INPCObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public INPCObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } public interface IObservedChange @@ -357,51 +504,37 @@ namespace ReactiveUI } public interface IPropertyBinderImplementation : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Bind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Bind uses expression trees which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, System.Func vmToViewConverter, System.Func viewToVmConverter, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Bind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Bind uses expression trees which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindTo uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindTo uses expression trees which may require unreferenced code")] System.IDisposable BindTo(System.IObservable observedChange, TTarget? target, System.Linq.Expressions.Expression> propertyExpression, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TTarget : class; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("OneWayBind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("OneWayBind uses expression trees which may require unreferenced code")] ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func selector) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("OneWayBind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("OneWayBind uses expression trees which may require unreferenced code")] ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor; } public interface IPropertyBindingHook { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction); } - public class IROObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger + public sealed class IROObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public IROObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } public interface IReactiveBinding : System.IDisposable @@ -427,10 +560,6 @@ namespace ReactiveUI { System.IObservable> Changed { get; } System.IObservable> Changing { get; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SuppressChangeNotifications uses extension methods that require dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SuppressChangeNotifications uses extension methods that may require unreferenced " + - "code")] System.IDisposable SuppressChangeNotifications(); } public interface IReactiveObject : Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging @@ -461,10 +590,17 @@ namespace ReactiveUI System.IObservable ObserveErrorChanged { get; } System.IObservable ObserveHasErrors { get; } T Value { get; set; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Refresh uses RaisePropertyChanged which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refresh uses RaisePropertyChanged which may require unreferenced code")] void Refresh(); } + public interface IRegistrar + { + void Register(System.Func factory, string? contract = null) + where TService : class; + void RegisterConstant(System.Func factory, string? contract = null) + where TService : class; + void RegisterLazySingleton<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TService>(System.Func factory, string? contract = null) + where TService : class; + } public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { ReactiveUI.IScreen HostScreen { get; } @@ -476,26 +612,24 @@ namespace ReactiveUI } public interface ISetMethodBindingConverter : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] int GetAffinityForObjects(System.Type? fromType, System.Type? toType); object? PerformSet(object? toTarget, object? newValue, object?[]? arguments); } public interface ISuspensionDriver { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvalidateState uses JsonSerializer which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvalidateState uses JsonSerializer which may require unreferenced code")] System.IObservable InvalidateState(); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("LoadState implementations may use serialization which requires dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("LoadState implementations may use serialization which may require unreferenced co" + - "de")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] System.IObservable LoadState(); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SaveState implementations may use serialization which requires dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SaveState implementations may use serialization which may require unreferenced co" + - "de")] - System.IObservable SaveState(object state); + System.IObservable LoadState(System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo); + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + System.IObservable SaveState(T state); + System.IObservable SaveState(T state, System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo); } public interface ISuspensionHost : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { @@ -518,47 +652,57 @@ namespace ReactiveUI } public interface IViewLocator : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ResolveView uses reflection and type discovery which require dynamic code generat" + - "ion")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ResolveView uses reflection and type discovery which may require unreferenced cod" + - "e")] - ReactiveUI.IViewFor? ResolveView(T? viewModel, string? contract = null); + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMe" + + "mbersAttribute, or generic constraints), trimming can\'t validate that the requir" + + "ements of those annotations are met.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to determine the view model type at runtime, which ma" + + "y be incompatible with trimming.")] + ReactiveUI.IViewFor? ResolveView(object? instance, string? contract = null); + ReactiveUI.IViewFor? ResolveView(string? contract = null) + where TViewModel : class; + } + public interface IViewModule + { + void RegisterViews(ReactiveUI.DefaultViewLocator locator); } public interface IWantsToRegisterStuff { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Uses reflection to create instances of types.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection to create instances of types.")] - void Register(System.Action, System.Type> registerFunction); + void Register(ReactiveUI.IRegistrar registrar); + } + public sealed class IntegerToNullableIntegerTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public IntegerToNullableIntegerTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(int from, object? conversionHint, out int? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class IntegerToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class IntegerToStringTypeConverter : ReactiveUI.BindingTypeConverter { public IntegerToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(int from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public class InteractionBinderImplementation : ReactiveUI.IInteractionBinderImplementation, Splat.IEnableLogger { public InteractionBinderImplementation() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.Threading.Tasks.Task> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.IObservable> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } } public static class InteractionBindingMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression binding which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression binding which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static System.IDisposable BindInteraction(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression>> propertyName, System.Func, System.Threading.Tasks.Task> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression binding which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression binding which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static System.IDisposable BindInteraction(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression>> propertyName, System.Func, System.IObservable> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } @@ -580,11 +724,20 @@ namespace ReactiveUI public System.IDisposable RegisterHandler(System.Func, System.Threading.Tasks.Task> handler) { } public System.IDisposable RegisterHandler(System.Func, System.IObservable> handler) { } } - public class LongToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class LongToNullableLongTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public LongToNullableLongTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(long from, object? conversionHint, out long? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class LongToStringTypeConverter : ReactiveUI.BindingTypeConverter { public LongToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(long from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public class MessageBus : ReactiveUI.IMessageBus, Splat.IEnableLogger { @@ -599,151 +752,199 @@ namespace ReactiveUI } public static class MutableDependencyResolverExtensions { - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification="Generic registration does not use dynamic code")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification="Generic registration does not use reflection")] public static Splat.IMutableDependencyResolver RegisterSingletonViewForViewModel(this Splat.IMutableDependencyResolver resolver, string? contract = null) where TView : class, ReactiveUI.IViewFor, new () where TViewModel : class { } - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification="Generic registration does not use dynamic code")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification="Generic registration does not use reflection")] public static Splat.IMutableDependencyResolver RegisterViewForViewModel(this Splat.IMutableDependencyResolver resolver, string? contract = null) where TView : class, ReactiveUI.IViewFor, new () where TViewModel : class { } } - public class NullableByteToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableBooleanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableBooleanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(bool? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableByteToByteTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableByteToByteTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(byte? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out byte result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableByteToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableByteToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(byte? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDateOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableDateOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateOnly? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDateTimeOffsetToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableDateTimeOffsetToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTimeOffset? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDateTimeToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableDateTimeToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTime? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDecimalToDecimalTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableDecimalToDecimalTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(decimal? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out decimal result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class NullableDecimalToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableDecimalToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableDecimalToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(decimal? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } - public class NullableDoubleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableDoubleToDoubleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableDoubleToDoubleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(double? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out double result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableDoubleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableDoubleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(double? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableGuidToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableGuidToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.Guid? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableIntegerToIntegerTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableIntegerToIntegerTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(int? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out int result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class NullableIntegerToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableIntegerToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableIntegerToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(int? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } - public class NullableLongToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableLongToLongTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableLongToLongTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(long? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out long result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableLongToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableLongToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(long? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } - public class NullableShortToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableShortToShortTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableShortToShortTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(short? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out short result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableShortToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableShortToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(short? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableSingleToSingleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableSingleToSingleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(float? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out float result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class NullableSingleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableSingleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableSingleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(float? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableTimeOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableTimeOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeOnly? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableTimeSpanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableTimeSpanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeSpan? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } public static class OAPHCreationHelperMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, out ReactiveUI.ObservableAsPropertyHelper result, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, TRet initialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, out ReactiveUI.ObservableAsPropertyHelper result, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, TRet initialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, out ReactiveUI.ObservableAsPropertyHelper result, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, out ReactiveUI.ObservableAsPropertyHelper result, TRet initialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, out ReactiveUI.ObservableAsPropertyHelper result, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } } public sealed class ObservableAsPropertyHelper : ReactiveUI.IHandleObservableErrors, Splat.IEnableLogger, System.IDisposable { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public ObservableAsPropertyHelper(System.IObservable observable, System.Action onChanged, T? initialValue = default, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public ObservableAsPropertyHelper(System.IObservable observable, System.Action onChanged, System.Action? onChanging = null, System.Func? getInitialValue = null, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public ObservableAsPropertyHelper(System.IObservable observable, System.Action onChanged, System.Action? onChanging = null, T? initialValue = default, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) { } public bool IsSubscribed { get; } public System.IObservable ThrownExceptions { get; } public T Value { get; } public void Dispose() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public static ReactiveUI.ObservableAsPropertyHelper Default(T? initialValue = default, System.Reactive.Concurrency.IScheduler? scheduler = null) { } } public static class ObservableFuncMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static System.IObservable ToObservable(this System.Linq.Expressions.Expression> expression, TSource? source, bool beforeChange = false, bool skipInitial = false) { } } public static class ObservableLoggingMixin @@ -769,24 +970,12 @@ namespace ReactiveUI } public static class ObservedChangedMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("TryGetValue uses expression chain analysis and reflection which require dynamic c" + - "ode generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TryGetValue uses expression chain analysis and reflection which may reference mem" + - "bers that could be trimmed.")] public static string GetPropertyName(this ReactiveUI.IObservedChange item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetValue uses expression chain analysis and reflection which require dynamic code" + - " generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetValue uses expression chain analysis and reflection which may reference member" + - "s that could be trimmed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static TValue GetValue(this ReactiveUI.IObservedChange item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetValueOrDefault uses expression chain analysis and reflection which require dyn" + - "amic code generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetValueOrDefault uses expression chain analysis and reflection which may referen" + - "ce members that could be trimmed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static TValue? GetValueOrDefault(this ReactiveUI.IObservedChange item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Value method uses GetValue which requires expression chain analysis and reflectio" + - "n.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Value method uses GetValue which may reference members that could be trimmed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable Value(this System.IObservable> item) { } } public static class OrderedComparer @@ -801,185 +990,104 @@ namespace ReactiveUI public static System.Collections.Generic.IComparer OrderByDescending(System.Func selector) { } public static System.Collections.Generic.IComparer OrderByDescending(System.Func selector, System.Collections.Generic.IComparer comparer) { } } - public class POCOObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger + public sealed class POCOObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public POCOObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses reflection and type analysis")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses reflection and type analysis")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } - public static class PlatformRegistrationManager - { - public static void SetRegistrationNamespaces(params ReactiveUI.RegistrationNamespace[] namespaces) { } - } public class PlatformRegistrations : ReactiveUI.IWantsToRegisterStuff { public PlatformRegistrations() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Platform registration uses ComponentModelTypeConverter and RxApp which require dy" + - "namic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Platform registration uses ComponentModelTypeConverter and RxApp which may requir" + - "e unreferenced code")] - public void Register(System.Action, System.Type> registerFunction) { } + public void Register(ReactiveUI.IRegistrar registrar) { } } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflectio" + + "n-based invocation.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types and expression graphs which may be trimmed.")] public class PropertyBinderImplementation : ReactiveUI.IPropertyBinderImplementation, Splat.IEnableLogger { public PropertyBinderImplementation() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] public ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, System.Func vmToViewConverter, System.Func viewToVmConverter, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] public ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] public System.IDisposable BindTo(System.IObservable observedChange, TTarget? target, System.Linq.Expressions.Expression> propertyExpression, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TTarget : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] public ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func selector) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] public ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflectio" + + "n-based invocation.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class PropertyBindingMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func vmToViewConverter, System.Func viewToVmConverter) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, System.Func vmToViewConverter, System.Func viewToVmConverter, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] public static System.IDisposable BindTo(this System.IObservable @this, TTarget? target, System.Linq.Expressions.Expression> property, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TTarget : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] public static ReactiveUI.IReactiveBinding OneWayBind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func selector) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] public static ReactiveUI.IReactiveBinding OneWayBind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } } public static class ReactiveCommand { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("CreateCombined uses CombinedReactiveCommand which requires dynamic code generatio" + - "n.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CreateCombined uses CombinedReactiveCommand which may require unreferenced code.")] public static ReactiveUI.CombinedReactiveCommand CreateCombined(System.Collections.Generic.IEnumerable> childCommands, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromObservable(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromObservable(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } } public abstract class ReactiveCommandBase : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveCommand, ReactiveUI.IReactiveCommand, System.IDisposable, System.IObservable, System.Windows.Input.ICommand @@ -1001,28 +1109,18 @@ namespace ReactiveUI { public static System.IDisposable InvokeCommand(this System.IObservable item, System.Windows.Input.ICommand? command) { } public static System.IDisposable InvokeCommand(this System.IObservable item, ReactiveUI.ReactiveCommandBase? command) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvokeCommand uses WhenAnyValue which requires dynamic code generation for expres" + - "sion tree analysis.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvokeCommand uses WhenAnyValue which may reference members that could be trimmed" + - ".")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable InvokeCommand(this System.IObservable item, TTarget? target, System.Linq.Expressions.Expression> commandProperty) where TTarget : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvokeCommand uses WhenAnyValue which requires dynamic code generation for expres" + - "sion tree analysis.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvokeCommand uses WhenAnyValue which may reference members that could be trimmed" + - ".")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable InvokeCommand(this System.IObservable item, TTarget? target, System.Linq.Expressions.Expression?>> commandProperty) where TTarget : class { } } public class ReactiveCommand : ReactiveUI.ReactiveCommandBase { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] protected ReactiveCommand([System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "Result", "Cancel"})] System.Func, System.Action>>> execute, System.IObservable? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] protected ReactiveCommand(System.Func> execute, System.IObservable? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { } public override System.IObservable CanExecute { get; } public override System.IObservable IsExecuting { get; } @@ -1032,54 +1130,45 @@ namespace ReactiveUI public override System.IObservable Execute(TParam parameter) { } public override System.IDisposable Subscribe(System.IObserver observer) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static class ReactiveNotifyPropertyChangedMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, bool beforeChange) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName, bool beforeChange) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, bool beforeChange, bool skipInitial) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName, bool beforeChange, bool skipInitial) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, bool beforeChange, bool skipInitial, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName, bool beforeChange, bool skipInitial, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, System.Func selector, bool beforeChange) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange, bool skipInitial) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange, bool skipInitial, bool suppressWarnings) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange, bool skipInitial, bool suppressWarnings, bool isDistinct) { } } [System.Runtime.Serialization.DataContract] @@ -1105,8 +1194,6 @@ namespace ReactiveUI public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; public bool AreChangeNotificationsEnabled() { } public System.IDisposable DelayChangeNotifications() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] public System.IDisposable SuppressChangeNotifications() { } } public class ReactivePropertyChangedEventArgs : System.ComponentModel.PropertyChangedEventArgs, ReactiveUI.IReactivePropertyChangedEventArgs @@ -1121,32 +1208,17 @@ namespace ReactiveUI } public static class ReactivePropertyMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses DataAnnotations validation which requires dynamic code generation" + - ".")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses DataAnnotations validation which may require unreferenced code.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("DataAnnotations validation uses reflection to discover attributes and is not trim" + + "-safe. Use manual validation for AOT scenarios.")] public static ReactiveUI.ReactiveProperty AddValidation(this ReactiveUI.ReactiveProperty self, System.Linq.Expressions.Expression?>> selfSelector) { } public static System.IObservable ObserveValidationErrors(this ReactiveUI.ReactiveProperty self) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses ReactiveObject and RxApp which require dynam" + - "ic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses ReactiveObject and RxApp which may require u" + - "nreferenced code")] [System.Runtime.Serialization.DataContract] public class ReactiveProperty : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty, System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable, System.Reactive.Disposables.ICancelable { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] public ReactiveProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] public ReactiveProperty(T? initialValue) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] public ReactiveProperty(T? initialValue, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses ReactiveObject and RxApp which require dynam" + - "ic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses ReactiveObject and RxApp which may require u" + - "nreferenced code")] public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { } public bool HasErrors { get; } public bool IsDisposed { get; } @@ -1156,30 +1228,16 @@ namespace ReactiveUI [System.Text.Json.Serialization.JsonInclude] public T Value { get; set; } public event System.EventHandler? ErrorsChanged; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func, System.IObservable> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func, System.IObservable> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func validator, bool ignoreInitialError = false) { } public void CheckValidation() { } public void Dispose() { } protected virtual void Dispose(bool disposing) { } public System.Collections.IEnumerable? GetErrors(string? propertyName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Refresh uses RaisePropertyChanged which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refresh uses RaisePropertyChanged which may require unreferenced code")] public void Refresh() { } public System.IDisposable Subscribe(System.IObserver observer) { } public static ReactiveUI.ReactiveProperty Create() { } @@ -1190,10 +1248,6 @@ namespace ReactiveUI [System.Runtime.Serialization.DataContract] public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveRecord constructor uses extension methods that require dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveRecord constructor uses extension methods that may require unreferenced c" + - "ode")] public ReactiveRecord() { } [System.ComponentModel.Browsable(false)] [System.ComponentModel.DataAnnotations.Display(AutoGenerateField=false, AutoGenerateFilter=false, Order=-1)] @@ -1212,72 +1266,37 @@ namespace ReactiveUI public System.IObservable ThrownExceptions { get; } public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AreChangeNotificationsEnabled uses extension methods that require dynamic code ge" + - "neration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AreChangeNotificationsEnabled uses extension methods that may require unreference" + - "d code")] public bool AreChangeNotificationsEnabled() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("DelayChangeNotifications uses extension methods that require dynamic code generat" + - "ion")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("DelayChangeNotifications uses extension methods that may require unreferenced cod" + - "e")] public System.IDisposable DelayChangeNotifications() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SuppressChangeNotifications uses extension methods that require dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SuppressChangeNotifications uses extension methods that may require unreferenced " + - "code")] public System.IDisposable SuppressChangeNotifications() { } } public static class Reflection { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression tree analysis requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression tree analysis may reference members that could be trimmed")] public static string ExpressionToPropertyNames(System.Linq.Expressions.Expression? expression) { } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event access may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Reflects over custom delegate Invoke signature; members may be trimmed.")] public static System.Type GetEventArgsTypeForEvent([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type type, string? eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member access may reference members that could be trimmed")] public static System.Func? GetValueFetcherForProperty(System.Reflection.MemberInfo? member) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member access may reference members that could be trimmed")] public static System.Func GetValueFetcherOrThrow(System.Reflection.MemberInfo? member) { } - public static System.Action GetValueSetterForProperty(System.Reflection.MemberInfo? member) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member access may reference members that could be trimmed")] - public static System.Action? GetValueSetterOrThrow(System.Reflection.MemberInfo? member) { } + public static System.Action? GetValueSetterForProperty(System.Reflection.MemberInfo? member) { } + public static System.Action GetValueSetterOrThrow(System.Reflection.MemberInfo? member) { } public static bool IsStatic(this System.Reflection.PropertyInfo item) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Resolves types by name and loads assemblies; types may be trimmed.")] public static System.Type? ReallyFindType(string? type, bool throwOnFailure) { } public static System.Linq.Expressions.Expression Rewrite(System.Linq.Expressions.Expression? expression) { } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Method access may reference members that could be trimmed")] + public static void ThrowIfMethodsNotOverloaded(string callingTypeName, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type targetType, params string[] methodsToCheck) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Inspects declared methods on a runtime type; members may be trimmed.")] public static void ThrowIfMethodsNotOverloaded(string callingTypeName, object targetObject, params string[] methodsToCheck) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TryGetAllValuesForPropertyChain(out ReactiveUI.IObservedChange[] changeValues, object? current, System.Collections.Generic.IEnumerable expressionChain) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TryGetValueForPropertyChain(out TValue changeValue, object? current, System.Collections.Generic.IEnumerable expressionChain) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TrySetValueToPropertyChain(object? target, System.Collections.Generic.IEnumerable expressionChain, TValue value, bool shouldThrow = true) { } } - public enum RegistrationNamespace - { - None = 0, - Winforms = 1, - Wpf = 2, - Uno = 3, - UnoWinUI = 4, - Blazor = 5, - Drawing = 6, - Avalonia = 7, - Maui = 8, - Uwp = 9, - WinUI = 10, - } public class Registrations : ReactiveUI.IWantsToRegisterStuff { public Registrations() { } - public void Register(System.Action, System.Type> registerFunction) { } + public void Register(ReactiveUI.IRegistrar registrar) { } } public static class RoutableViewModelMixin { @@ -1285,13 +1304,9 @@ namespace ReactiveUI public static System.IObservable WhenNavigatedToObservable(this ReactiveUI.IRoutableViewModel item) { } public static System.IObservable WhenNavigatingFromObservable(this ReactiveUI.IRoutableViewModel item) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutingState uses RxApp and ReactiveCommand which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutingState uses RxApp and ReactiveCommand which may require unreferenced code")] [System.Runtime.Serialization.DataContract] public class RoutingState : ReactiveUI.ReactiveObject { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutingState uses ReactiveCommand which requires dynamic code generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutingState uses ReactiveCommand which may require unreferenced code.")] public RoutingState(System.Reactive.Concurrency.IScheduler? scheduler = null) { } [System.Runtime.Serialization.IgnoreDataMember] [System.Text.Json.Serialization.JsonIgnore] @@ -1318,21 +1333,29 @@ namespace ReactiveUI where T : ReactiveUI.IRoutableViewModel { } public static ReactiveUI.IRoutableViewModel? GetCurrentViewModel(this ReactiveUI.RoutingState item) { } } - public static class RxApp + public static class RxCacheSize { - public const int BigCacheLimit = 256; - public const int SmallCacheLimit = 64; - public static System.IObserver DefaultExceptionHandler { get; set; } - public static System.Reactive.Concurrency.IScheduler MainThreadScheduler { get; set; } - public static bool SuppressViewCommandBindingMessage { get; set; } - public static ReactiveUI.ISuspensionHost SuspensionHost { get; set; } - public static System.Reactive.Concurrency.IScheduler TaskpoolScheduler { get; set; } + public static int BigCacheLimit { get; } + public static int SmallCacheLimit { get; } + } + public static class RxConverters + { + public static ReactiveUI.ConverterService Current { get; } } public static class RxSchedulers { public static System.Reactive.Concurrency.IScheduler MainThreadScheduler { get; set; } + public static bool SuppressViewCommandBindingMessage { get; set; } public static System.Reactive.Concurrency.IScheduler TaskpoolScheduler { get; set; } } + public static class RxState + { + public static System.IObserver DefaultExceptionHandler { get; } + } + public static class RxSuspension + { + public static ReactiveUI.ISuspensionHost SuspensionHost { get; } + } public class ScheduledSubject : System.IDisposable, System.IObservable, System.IObserver, System.Reactive.Subjects.ISubject, System.Reactive.Subjects.ISubject { public ScheduledSubject(System.Reactive.Concurrency.IScheduler scheduler, System.IObserver? defaultObserver = null, System.Reactive.Subjects.ISubject? defaultSubject = null) { } @@ -1343,42 +1366,283 @@ namespace ReactiveUI public void OnNext(T value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } - public class ShortToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class SetMethodBindingConverterRegistry + { + public SetMethodBindingConverterRegistry() { } + public System.Collections.Generic.IEnumerable GetAllConverters() { } + public void Register(ReactiveUI.ISetMethodBindingConverter converter) { } + public ReactiveUI.ISetMethodBindingConverter? TryGetConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? toType) { } + } + public sealed class ShortToNullableShortTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public ShortToNullableShortTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(short from, object? conversionHint, out short? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class ShortToStringTypeConverter : ReactiveUI.BindingTypeConverter { public ShortToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(short from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed class SingleInstanceViewAttribute : System.Attribute { public SingleInstanceViewAttribute() { } } - public class SingleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class SingleToNullableSingleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public SingleToNullableSingleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(float from, object? conversionHint, out float? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class SingleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public SingleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(float from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } - public class StringConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class StringConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger { public StringConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result) { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class StringToBooleanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToBooleanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out bool result) { } + } + public sealed class StringToByteTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToByteTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out byte result) { } + } + public sealed class StringToDateOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDateOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.DateOnly result) { } + } + public sealed class StringToDateTimeOffsetTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDateTimeOffsetTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.DateTimeOffset result) { } + } + public sealed class StringToDateTimeTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDateTimeTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.DateTime result) { } + } + public sealed class StringToDecimalTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDecimalTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out decimal result) { } + } + public sealed class StringToDoubleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDoubleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out double result) { } + } + public sealed class StringToGuidTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToGuidTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Guid result) { } + } + public sealed class StringToIntegerTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToIntegerTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out int result) { } + } + public sealed class StringToLongTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToLongTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out long result) { } + } + public sealed class StringToNullableBooleanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableBooleanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out bool? result) { } + } + public sealed class StringToNullableByteTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableByteTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out byte? result) { } + } + public sealed class StringToNullableDateOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDateOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.DateOnly? result) { } + } + public sealed class StringToNullableDateTimeOffsetTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDateTimeOffsetTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.DateTimeOffset? result) { } + } + public sealed class StringToNullableDateTimeTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDateTimeTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.DateTime? result) { } + } + public sealed class StringToNullableDecimalTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDecimalTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out decimal? result) { } + } + public sealed class StringToNullableDoubleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDoubleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out double? result) { } + } + public sealed class StringToNullableGuidTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableGuidTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.Guid? result) { } + } + public sealed class StringToNullableIntegerTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableIntegerTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out int? result) { } + } + public sealed class StringToNullableLongTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableLongTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out long? result) { } + } + public sealed class StringToNullableShortTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableShortTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out short? result) { } + } + public sealed class StringToNullableSingleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableSingleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out float? result) { } + } + public sealed class StringToNullableTimeOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableTimeOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.TimeOnly? result) { } + } + public sealed class StringToNullableTimeSpanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableTimeSpanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.TimeSpan? result) { } + } + public sealed class StringToShortTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToShortTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out short result) { } + } + public sealed class StringToSingleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToSingleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out float result) { } + } + public sealed class StringToTimeOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToTimeOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.TimeOnly result) { } + } + public sealed class StringToTimeSpanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToTimeSpanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.TimeSpan result) { } + } + public sealed class StringToUriTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToUriTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Uri? result) { } } public static class SuspensionHostExtensions { + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. Prefer GetAppState(ISuspensionHost) used with SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. Prefer GetAppState(ISuspensionHost) used with SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] public static T GetAppState(this ReactiveUI.ISuspensionHost item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObserveAppState uses WhenAny which requires dynamic code generation for expressio" + - "n tree analysis")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObserveAppState uses WhenAny which may reference members that could be trimmed")] + public static TAppState GetAppState(this ReactiveUI.Interfaces.ISuspensionHost item) + where TAppState : class { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This overload uses WhenAny, which can require unreferenced/dynamic code in trimmi" + + "ng/AOT scenarios. Prefer ObserveAppState(ISuspensionHost) " + + "for trimming/AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This overload uses WhenAny, which can require unreferenced/dynamic code in trimmi" + + "ng/AOT scenarios. Prefer ObserveAppState(ISuspensionHost) " + + "for trimming/AOT scenarios.")] public static System.IObservable ObserveAppState(this ReactiveUI.ISuspensionHost item) where T : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SetupDefaultSuspendResume uses ISuspensionDriver which may require dynamic code g" + - "eneration for serialization")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SetupDefaultSuspendResume uses ISuspensionDriver which may require unreferenced c" + - "ode for serialization")] + public static System.IObservable ObserveAppState(this ReactiveUI.Interfaces.ISuspensionHost item) + where TAppState : class { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This overload may invoke ISuspensionDriver.LoadState()/SaveState(T), which are" + + " commonly reflection-based. Prefer SetupDefaultSuspendResume(..., Jso" + + "nTypeInfo, ...) for trimming/AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This overload may invoke ISuspensionDriver.LoadState()/SaveState(T), which are" + + " commonly reflection-based. Prefer SetupDefaultSuspendResume(..., Jso" + + "nTypeInfo, ...) for trimming/AOT scenarios.")] public static System.IDisposable SetupDefaultSuspendResume(this ReactiveUI.ISuspensionHost item, ReactiveUI.ISuspensionDriver? driver = null) { } + public static System.IDisposable SetupDefaultSuspendResume(this ReactiveUI.Interfaces.ISuspensionHost item, System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo, ReactiveUI.ISuspensionDriver? driver = null) + where TAppState : class { } + } + public class SuspensionHost : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveObject, ReactiveUI.ISuspensionHost, ReactiveUI.Interfaces.ISuspensionHost, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IDisposable + { + public SuspensionHost() { } + public TAppState AppStateValue { get; set; } + public System.IObservable AppStateValueChanged { get; } + public System.Func? CreateNewAppStateTyped { get; set; } + public System.IObservable IsContinuing { get; set; } + public System.IObservable IsLaunchingNew { get; set; } + public System.IObservable IsResuming { get; set; } + public System.IObservable IsUnpausing { get; set; } + public System.IObservable ShouldInvalidateState { get; set; } + public System.IObservable ShouldPersistState { get; set; } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + } + public sealed class TimeOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public TimeOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeOnly from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class TimeSpanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public TimeSpanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeSpan from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public enum TriggerUpdate { @@ -1400,6 +1664,12 @@ namespace ReactiveUI public TInput Input { get; } public ReactiveUI.Interaction? Interaction { get; } } + public sealed class UriToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public UriToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.Uri? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed class ViewContractAttribute : System.Attribute { @@ -1408,23 +1678,18 @@ namespace ReactiveUI } public static class ViewForMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Action> block) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Func> block) { } public static void WhenActivated(this ReactiveUI.IActivatableViewModel item, System.Action> block) { } public static void WhenActivated(this ReactiveUI.IActivatableViewModel item, System.Action block) { } public static void WhenActivated(this ReactiveUI.IActivatableViewModel item, System.Func> block) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Action> block, ReactiveUI.IViewFor view) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Action block, ReactiveUI.IViewFor? view = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Func> block, ReactiveUI.IViewFor? view) { } } public static class ViewLocator @@ -1437,6 +1702,18 @@ namespace ReactiveUI public ViewLocatorNotFoundException(string message) { } public ViewLocatorNotFoundException(string message, System.Exception innerException) { } } + public sealed class ViewMappingBuilder + { + public ReactiveUI.ViewMappingBuilder Map(string? contract = null) + where TViewModel : class + where TView : class, ReactiveUI.IViewFor, new () { } + public ReactiveUI.ViewMappingBuilder Map(System.Func factory, string? contract = null) + where TViewModel : class + where TView : class, ReactiveUI.IViewFor { } + public ReactiveUI.ViewMappingBuilder MapFromServiceLocator(string? contract = null) + where TViewModel : class + where TView : class, ReactiveUI.IViewFor { } + } public sealed class ViewModelActivator : System.IDisposable { public ViewModelActivator() { } @@ -1454,716 +1731,205 @@ namespace ReactiveUI public System.IDisposable Schedule(TState state, System.DateTimeOffset dueTime, System.Func action) { } public System.IDisposable Schedule(TState state, System.TimeSpan dueTime, System.Func action) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class WhenAnyMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, System.Func, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, System.Func, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, System.Func, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, System.Func, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Func, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Func, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Func, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Func, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Linq.Expressions.Expression? property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Linq.Expressions.Expression? property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string propertyName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string propertyName, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func selector, bool isDistinct) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class WhenAnyObservableMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11, System.Linq.Expressions.Expression?>> obs12) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11, System.Linq.Expressions.Expression?>> obs12, System.Func selector) where TSender : class { } } @@ -2172,6 +1938,7 @@ namespace ReactiveUI.Builder { public static class BuilderMixins { + public static ReactiveUI.Builder.IReactiveUIBuilder BuildApp(this Splat.Builder.IAppBuilder appBuilder) { } public static ReactiveUI.Builder.IReactiveUIBuilder ConfigureMessageBus(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action configure) { } public static ReactiveUI.Builder.IReactiveUIBuilder ConfigureSuspensionDriver(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action configure) { } public static ReactiveUI.Builder.IReactiveUIBuilder ConfigureViewLocator(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action configure) { } @@ -2187,9 +1954,18 @@ namespace ReactiveUI.Builder where TViewModel : class, ReactiveUI.IReactiveObject { } public static ReactiveUI.Builder.IReactiveUIBuilder RegisterViewModel(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder) where TViewModel : class, ReactiveUI.IReactiveObject, new () { } + public static ReactiveUI.Builder.IReactiveUIBuilder RegisterViews(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Action configure) { } public static ReactiveUI.Builder.IReactiveUIBuilder UsingSplatBuilder(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action? appBuilder) { } public static ReactiveUI.Builder.IReactiveUIBuilder UsingSplatModule(this ReactiveUI.Builder.IReactiveUIBuilder builder, T registrationModule) where T : Splat.Builder.IModule { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.IBindingTypeConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func factory) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.BindingTypeConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func> factory) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverters(this ReactiveUI.Builder.IReactiveUIBuilder builder, params ReactiveUI.IBindingTypeConverter[] converters) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConvertersFrom(this ReactiveUI.Builder.IReactiveUIBuilder builder, Splat.IReadonlyDependencyResolver resolver) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.IBindingFallbackConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func factory) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } @@ -2207,15 +1983,18 @@ namespace ReactiveUI.Builder public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithMainThreadScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + public static ReactiveUI.Builder.IReactiveUIBuilder WithMessageBus(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, ReactiveUI.IMessageBus messageBus) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithPlatformModule(this ReactiveUI.Builder.IReactiveUIBuilder builder) where T : ReactiveUI.IWantsToRegisterStuff, new () { } public static ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Action configureAction) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithRegistrationOnBuild(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Action configureAction) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.ISetMethodBindingConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func factory) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithTaskPoolScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + public static ReactiveUI.Builder.IReactiveUIBuilder WithViewModule(this ReactiveUI.Builder.IReactiveUIBuilder builder) + where TModule : ReactiveUI.IViewModule, new () { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] public static ReactiveUI.Builder.IReactiveUIBuilder WithViewsFromAssembly(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Reflection.Assembly assembly) { } } public interface IReactiveUIBuilder : Splat.Builder.IAppBuilder @@ -2238,6 +2017,8 @@ namespace ReactiveUI.Builder where TViewModel : class, ReactiveUI.IReactiveObject, new (); ReactiveUI.Builder.IReactiveUIBuilder UsingSplatModule(T registrationModule) where T : Splat.Builder.IModule; + ReactiveUI.Builder.IReactiveUIBuilder WithCacheSizes(int smallCacheLimit, int bigCacheLimit); + ReactiveUI.Builder.IReactiveUIBuilder WithExceptionHandler(System.IObserver exceptionHandler); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); @@ -2255,19 +2036,17 @@ namespace ReactiveUI.Builder ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIBuilder WithMainThreadScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + ReactiveUI.Builder.IReactiveUIBuilder WithMessageBus(ReactiveUI.IMessageBus messageBus); ReactiveUI.Builder.IReactiveUIBuilder WithPlatformModule() where T : ReactiveUI.IWantsToRegisterStuff, new (); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ProcessRegistrationForNamespace uses reflection to locate types which may be trim" + - "med.")] ReactiveUI.Builder.IReactiveUIBuilder WithPlatformServices(); ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(System.Action configureAction); ReactiveUI.Builder.IReactiveUIBuilder WithRegistrationOnBuild(System.Action configureAction); + ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost(); + ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost(); ReactiveUI.Builder.IReactiveUIBuilder WithTaskPoolScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] ReactiveUI.Builder.IReactiveUIBuilder WithViewsFromAssembly(System.Reflection.Assembly assembly); } public interface IReactiveUIInstance : Splat.Builder.IAppInstance @@ -2278,6 +2057,7 @@ namespace ReactiveUI.Builder public sealed class ReactiveUIBuilder : Splat.Builder.AppBuilder, ReactiveUI.Builder.IReactiveUIBuilder, ReactiveUI.Builder.IReactiveUIInstance, Splat.Builder.IAppBuilder, Splat.Builder.IAppInstance { public ReactiveUIBuilder(Splat.IMutableDependencyResolver resolver, Splat.IReadonlyDependencyResolver? current) { } + public ReactiveUI.ConverterService ConverterService { get; } public System.Reactive.Concurrency.IScheduler? MainThreadScheduler { get; } public System.Reactive.Concurrency.IScheduler? TaskpoolScheduler { get; } public ReactiveUI.Builder.IReactiveUIInstance BuildApp() { } @@ -2299,9 +2079,16 @@ namespace ReactiveUI.Builder public ReactiveUI.Builder.IReactiveUIBuilder UsingSplatBuilder(System.Action appBuilder) { } public ReactiveUI.Builder.IReactiveUIBuilder UsingSplatModule(T registrationModule) where T : Splat.Builder.IModule { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] + public ReactiveUI.Builder.IReactiveUIBuilder WithCacheSizes(int smallCacheLimit, int bigCacheLimit) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(ReactiveUI.IBindingTypeConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(System.Func factory) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(ReactiveUI.BindingTypeConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(System.Func> factory) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConvertersFrom(Splat.IReadonlyDependencyResolver resolver) { } public override Splat.Builder.IAppBuilder WithCoreServices() { } + public ReactiveUI.Builder.IReactiveUIBuilder WithExceptionHandler(System.IObserver exceptionHandler) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(ReactiveUI.IBindingFallbackConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(System.Func factory) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } @@ -2319,23 +2106,35 @@ namespace ReactiveUI.Builder public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIBuilder WithMainThreadScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + public ReactiveUI.Builder.IReactiveUIBuilder WithMessageBus(ReactiveUI.IMessageBus messageBus) { } public ReactiveUI.Builder.IReactiveUIBuilder WithPlatformModule() where T : ReactiveUI.IWantsToRegisterStuff, new () { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] public ReactiveUI.Builder.IReactiveUIBuilder WithPlatformServices() { } + public ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(ReactiveUI.IWantsToRegisterStuff registration) { } public ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(System.Action configureAction) { } public ReactiveUI.Builder.IReactiveUIBuilder WithRegistrationOnBuild(System.Action configureAction) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(ReactiveUI.ISetMethodBindingConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(System.Func factory) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost() { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost() { } public ReactiveUI.Builder.IReactiveUIBuilder WithTaskPoolScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] public ReactiveUI.Builder.IReactiveUIBuilder WithViewsFromAssembly(System.Reflection.Assembly assembly) { } } public static class RxAppBuilder { public static ReactiveUI.Builder.ReactiveUIBuilder CreateReactiveUIBuilder() { } public static ReactiveUI.Builder.ReactiveUIBuilder CreateReactiveUIBuilder(this Splat.IMutableDependencyResolver resolver) { } + public static void EnsureInitialized() { } + } +} +namespace ReactiveUI.Interfaces +{ + public interface ISuspensionHost : ReactiveUI.IReactiveObject, ReactiveUI.ISuspensionHost, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging + { + TAppState AppStateValue { get; set; } + System.IObservable AppStateValueChanged { get; } + System.Func? CreateNewAppStateTyped { get; set; } } } \ No newline at end of file diff --git a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet8_0.verified.txt b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet8_0.verified.txt index a45b0b4dfd..27c8f9b9a5 100644 --- a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet8_0.verified.txt +++ b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet8_0.verified.txt @@ -7,18 +7,22 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Builder.WpfApp")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Drawing")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Maui")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Maui.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Test.Utilities")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.TestGuiMocks")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Testing")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Testing.Reactive")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Uno")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Uno.WinUI")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.WinForms.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.WinUI")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Winforms")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Wpf")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Wpf.Tests")] namespace ReactiveUI { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static class AutoPersistHelper { public static System.IDisposable ActOnEveryObject(this System.Collections.ObjectModel.ObservableCollection @this, System.Action onAdd, System.Action onRemove) @@ -30,19 +34,69 @@ namespace ReactiveUI public static System.IDisposable ActOnEveryObject(this TCollection collection, System.Action onAdd, System.Action onRemove) where TItem : ReactiveUI.IReactiveObject where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } - public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, System.TimeSpan? interval = default) + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + public static System.IDisposable AutoPersist<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(this T @this, System.Func> doPersist, System.TimeSpan? interval = default) where T : ReactiveUI.IReactiveObject { } - public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) + public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) where T : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + public static System.IDisposable AutoPersist<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TDontCare>(this T @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) + where T : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where T : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ReadOnlyObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ReadOnlyObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this TCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IDisposable AutoPersistCollection(this TCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IDisposable AutoPersistCollection(this TCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.Func metadataProvider, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static ReactiveUI.AutoPersistHelper.AutoPersistMetadata CreateMetadata<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] T>() + where T : ReactiveUI.IReactiveObject { } + public static System.Func CreateMetadataProvider<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] TItem>() + where TItem : ReactiveUI.IReactiveObject { } + public sealed class AutoPersistMetadata + { + public AutoPersistMetadata(bool hasDataContract, System.Collections.Generic.ISet persistablePropertyNames) { } + public bool HasDataContract { get; } + public System.Collections.Generic.ISet PersistablePropertyNames { get; } + } } public enum BindingDirection { @@ -50,17 +104,53 @@ namespace ReactiveUI TwoWay = 1, AsyncOneWay = 2, } - public class ByteToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class BindingFallbackConverterRegistry + { + public BindingFallbackConverterRegistry() { } + public System.Collections.Generic.IEnumerable GetAllConverters() { } + public void Register(ReactiveUI.IBindingFallbackConverter converter) { } + public ReactiveUI.IBindingFallbackConverter? TryGetConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType) { } + } + public sealed class BindingTypeConverterRegistry + { + public BindingTypeConverterRegistry() { } + public System.Collections.Generic.IEnumerable GetAllConverters() { } + public void Register(ReactiveUI.IBindingTypeConverter converter) { } + public ReactiveUI.IBindingTypeConverter? TryGetConverter(System.Type fromType, System.Type toType) { } + } + public abstract class BindingTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + protected BindingTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public abstract int GetAffinityForObjects(); + public abstract bool TryConvert(TFrom? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out TTo? result); + public bool TryConvertTyped(object? from, object? conversionHint, out object? result) { } + } + public sealed class BooleanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public BooleanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(bool from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class ByteToNullableByteTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public ByteToNullableByteTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(byte from, object? conversionHint, out byte? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class ByteToStringTypeConverter : ReactiveUI.BindingTypeConverter { public ByteToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(byte from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public class CanActivateViewFetcher : ReactiveUI.IActivationForViewFetcher { public CanActivateViewFetcher() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } public int GetAffinityForView(System.Type view) { } } @@ -73,10 +163,6 @@ namespace ReactiveUI } public class CombinedReactiveCommand : ReactiveUI.ReactiveCommandBase> { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("CombinedReactiveCommand uses RxApp and ReactiveCommand which require dynamic code" + - " generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CombinedReactiveCommand uses RxApp and ReactiveCommand which may require unrefere" + - "nced code.")] protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable> childCommands, System.IObservable? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } public override System.IObservable CanExecute { get; } public override System.IObservable IsExecuting { get; } @@ -88,40 +174,40 @@ namespace ReactiveUI } public static class CommandBinder { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, string? toEvent = null) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) + where TProp : System.Windows.Input.ICommand + where TControl : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) + where TProp : System.Windows.Input.ICommand + where TControl : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } + where TProp : System.Windows.Input.ICommand + where TControl : class { } } public class CommandBinderImplementation : Splat.IEnableLogger { public CommandBinderImplementation() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) + where TProp : System.Windows.Input.ICommand + where TControl : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } + where TProp : System.Windows.Input.ICommand + where TControl : class { } } public static class ComparerChainingExtensions { @@ -130,98 +216,156 @@ namespace ReactiveUI public static System.Collections.Generic.IComparer ThenByDescending(this System.Collections.Generic.IComparer? parent, System.Func selector) { } public static System.Collections.Generic.IComparer ThenByDescending(this System.Collections.Generic.IComparer? parent, System.Func selector, System.Collections.Generic.IComparer comparer) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Component model type conversion uses reflection and dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Component model type conversion may reference types that could be trimmed")] - public class ComponentModelTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class ComponentModelFallbackConverter : ReactiveUI.IBindingFallbackConverter, Splat.IEnableLogger + { + public ComponentModelFallbackConverter() { } + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification="The callers of this method ensure getting the converter is trim compatible - i.e." + + " the type is not Nullable.")] + public int GetAffinityForObjects([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType) { } + public bool TryConvert([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, object from, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public static class ConverterMigrationHelper { - public ComponentModelTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result) { } + [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { + "TypedConverters", + "FallbackConverters", + "SetMethodConverters"})] + public static System.ValueTuple, System.Collections.Generic.IList, System.Collections.Generic.IList> ExtractConverters(Splat.IReadonlyDependencyResolver resolver) { } + public static void ImportFrom(this ReactiveUI.ConverterService converterService, Splat.IReadonlyDependencyResolver resolver) { } + } + public sealed class ConverterService + { + public ConverterService() { } + public ReactiveUI.BindingFallbackConverterRegistry FallbackConverters { get; } + public ReactiveUI.SetMethodBindingConverterRegistry SetMethodConverters { get; } + public ReactiveUI.BindingTypeConverterRegistry TypedConverters { get; } + public object? ResolveConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType) { } + public ReactiveUI.ISetMethodBindingConverter? ResolveSetMethodConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? toType) { } } - public class CreatesCommandBindingViaCommandParameter : ReactiveUI.ICreatesCommandBinding + public sealed class CreatesCommandBindingViaCommandParameter : ReactiveUI.ICreatesCommandBinding { public CreatesCommandBindingViaCommandParameter() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindCommandToObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindCommandToObject uses methods that may require unreferenced code")] - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindCommandToObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindCommandToObject uses methods that may require unreferenced code")] - public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Property access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Property access may reference members that could be trimmed")] - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs { } public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } } - public class CreatesCommandBindingViaEvent : ReactiveUI.ICreatesCommandBinding + public sealed class CreatesCommandBindingViaEvent : ReactiveUI.ICreatesCommandBinding { public CreatesCommandBindingViaEvent() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Event binding requires dynamic code generation and reflection")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Event binding requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] - public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Event binding requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class { } + public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action addHandler, System.Action removeHandler) + where T : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs { } public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } } - public class DecimalToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class DateOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public DateOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateOnly from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class DateTimeOffsetToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public DateTimeOffsetToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTimeOffset from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class DateTimeToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public DateTimeToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTime from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class DecimalToNullableDecimalTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public DecimalToNullableDecimalTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(decimal from, object? conversionHint, out decimal? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class DecimalToStringTypeConverter : ReactiveUI.BindingTypeConverter { public DecimalToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(decimal from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public sealed class DefaultViewLocator : ReactiveUI.IViewLocator, Splat.IEnableLogger { - public System.Func ViewModelToViewFunc { get; set; } - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification="Mapping does not use dynamic code")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification="Mapping does not use reflection")] public ReactiveUI.DefaultViewLocator Map(System.Func factory, string? contract = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("View resolution uses reflection and type discovery")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("View resolution may reference types that could be trimmed")] - public ReactiveUI.IViewFor? ResolveView(T? viewModel, string? contract = null) { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMe" + + "mbersAttribute, or generic constraints), trimming can\'t validate that the requir" + + "ements of those annotations are met.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to determine the view model type at runtime, which ma" + + "y be incompatible with trimming.")] + public ReactiveUI.IViewFor? ResolveView(object? instance, string? contract = null) { } + public ReactiveUI.IViewFor? ResolveView(string? contract = null) + where TViewModel : class { } public ReactiveUI.DefaultViewLocator Unmap(string? contract = null) where TViewModel : class { } } public static class DependencyResolverMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InitializeReactiveUI uses reflection to locate types which may be trimmed.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InitializeReactiveUI uses reflection to locate types which may be trimmed.")] - public static void InitializeReactiveUI(this Splat.IMutableDependencyResolver resolver, params ReactiveUI.RegistrationNamespace[] registrationNamespaces) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RegisterViewsForViewModels scans the provided assembly and creates instances via " + - "reflection; this is not compatible with AOT.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RegisterViewsForViewModels uses reflection over types and members which may be tr" + - "immed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to register views explicitly.")] public static void RegisterViewsForViewModels(this Splat.IMutableDependencyResolver resolver, System.Reflection.Assembly assembly) { } } - public class DoubleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class DoubleToNullableDoubleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public DoubleToNullableDoubleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(double from, object? conversionHint, out double? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class DoubleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public DoubleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(double from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } - public class DummySuspensionDriver : ReactiveUI.ISuspensionDriver + public sealed class DummySuspensionDriver : ReactiveUI.ISuspensionDriver { public DummySuspensionDriver() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvalidateState uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvalidateState uses methods that may require unreferenced code")] public System.IObservable InvalidateState() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("LoadState uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("LoadState uses methods that may require unreferenced code")] - public System.IObservable LoadState() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SaveState uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SaveState uses methods that may require unreferenced code")] - public System.IObservable SaveState(object state) { } - } - public class EqualityTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] + public System.IObservable LoadState() { } + public System.IObservable LoadState(System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo) { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + public System.IObservable SaveState(T state) { } + public System.IObservable SaveState(T state, System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo) { } + } + public sealed class EqualityTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger { public EqualityTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result) { } - public static object? DoReferenceCast(object? from, System.Type targetType) { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=false, Inherited=false)] public sealed class ExcludeFromViewRegistrationAttribute : System.Attribute @@ -231,16 +375,16 @@ namespace ReactiveUI public static class ExpressionMixins { public static object?[]? GetArgumentsArray(this System.Linq.Expressions.Expression expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression chain analysis requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression chain analysis may reference members that could be trimmed")] public static System.Collections.Generic.IEnumerable GetExpressionChain(this System.Linq.Expressions.Expression expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member info access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member info access may reference members that could be trimmed")] public static System.Reflection.MemberInfo? GetMemberInfo(this System.Linq.Expressions.Expression expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression analysis requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression analysis may reference members that could be trimmed")] public static System.Linq.Expressions.Expression? GetParent(this System.Linq.Expressions.Expression expression) { } } + public sealed class GuidToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public GuidToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.Guid from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } public interface IActivatableView { } public interface IActivatableViewModel { @@ -248,15 +392,24 @@ namespace ReactiveUI } public interface IActivationForViewFetcher { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] System.IObservable GetActivationForView(ReactiveUI.IActivatableView view); int GetAffinityForView(System.Type view); } + public interface IBindingFallbackConverter : Splat.IEnableLogger + { + int GetAffinityForObjects([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType); + bool TryConvert([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, object from, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result); + } public interface IBindingTypeConverter : Splat.IEnableLogger { - int GetAffinityForObjects(System.Type fromType, System.Type toType); - bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result); + System.Type FromType { get; } + System.Type ToType { get; } + int GetAffinityForObjects(); + bool TryConvertTyped(object? from, object? conversionHint, out object? result); + } + public interface IBindingTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + bool TryConvert(TFrom? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out TTo? result); } public interface ICanActivate { @@ -272,24 +425,22 @@ namespace ReactiveUI } public interface ICreatesCommandBinding { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] - System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] - System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] - int GetAffinityForObject(System.Type type, bool hasEventTarget); + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class; + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class; + System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs; int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget); } public interface ICreatesObservableForProperty : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false); } public interface IHandleObservableErrors @@ -298,13 +449,11 @@ namespace ReactiveUI } public interface IInteractionBinderImplementation : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression evaluation which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression evaluation which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.Threading.Tasks.Task> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression evaluation which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression evaluation which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.IObservable> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor; @@ -334,11 +483,9 @@ namespace ReactiveUI public class INPCObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public INPCObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } public interface IObservedChange @@ -357,51 +504,37 @@ namespace ReactiveUI } public interface IPropertyBinderImplementation : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Bind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Bind uses expression trees which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, System.Func vmToViewConverter, System.Func viewToVmConverter, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Bind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Bind uses expression trees which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindTo uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindTo uses expression trees which may require unreferenced code")] System.IDisposable BindTo(System.IObservable observedChange, TTarget? target, System.Linq.Expressions.Expression> propertyExpression, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TTarget : class; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("OneWayBind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("OneWayBind uses expression trees which may require unreferenced code")] ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func selector) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("OneWayBind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("OneWayBind uses expression trees which may require unreferenced code")] ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor; } public interface IPropertyBindingHook { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction); } - public class IROObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger + public sealed class IROObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public IROObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } public interface IReactiveBinding : System.IDisposable @@ -427,10 +560,6 @@ namespace ReactiveUI { System.IObservable> Changed { get; } System.IObservable> Changing { get; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SuppressChangeNotifications uses extension methods that require dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SuppressChangeNotifications uses extension methods that may require unreferenced " + - "code")] System.IDisposable SuppressChangeNotifications(); } public interface IReactiveObject : Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging @@ -461,10 +590,17 @@ namespace ReactiveUI System.IObservable ObserveErrorChanged { get; } System.IObservable ObserveHasErrors { get; } T Value { get; set; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Refresh uses RaisePropertyChanged which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refresh uses RaisePropertyChanged which may require unreferenced code")] void Refresh(); } + public interface IRegistrar + { + void Register(System.Func factory, string? contract = null) + where TService : class; + void RegisterConstant(System.Func factory, string? contract = null) + where TService : class; + void RegisterLazySingleton<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TService>(System.Func factory, string? contract = null) + where TService : class; + } public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { ReactiveUI.IScreen HostScreen { get; } @@ -476,26 +612,24 @@ namespace ReactiveUI } public interface ISetMethodBindingConverter : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] int GetAffinityForObjects(System.Type? fromType, System.Type? toType); object? PerformSet(object? toTarget, object? newValue, object?[]? arguments); } public interface ISuspensionDriver { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvalidateState uses JsonSerializer which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvalidateState uses JsonSerializer which may require unreferenced code")] System.IObservable InvalidateState(); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("LoadState implementations may use serialization which requires dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("LoadState implementations may use serialization which may require unreferenced co" + - "de")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] System.IObservable LoadState(); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SaveState implementations may use serialization which requires dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SaveState implementations may use serialization which may require unreferenced co" + - "de")] - System.IObservable SaveState(object state); + System.IObservable LoadState(System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo); + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + System.IObservable SaveState(T state); + System.IObservable SaveState(T state, System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo); } public interface ISuspensionHost : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { @@ -518,47 +652,57 @@ namespace ReactiveUI } public interface IViewLocator : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ResolveView uses reflection and type discovery which require dynamic code generat" + - "ion")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ResolveView uses reflection and type discovery which may require unreferenced cod" + - "e")] - ReactiveUI.IViewFor? ResolveView(T? viewModel, string? contract = null); + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMe" + + "mbersAttribute, or generic constraints), trimming can\'t validate that the requir" + + "ements of those annotations are met.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to determine the view model type at runtime, which ma" + + "y be incompatible with trimming.")] + ReactiveUI.IViewFor? ResolveView(object? instance, string? contract = null); + ReactiveUI.IViewFor? ResolveView(string? contract = null) + where TViewModel : class; + } + public interface IViewModule + { + void RegisterViews(ReactiveUI.DefaultViewLocator locator); } public interface IWantsToRegisterStuff { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Uses reflection to create instances of types.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection to create instances of types.")] - void Register(System.Action, System.Type> registerFunction); + void Register(ReactiveUI.IRegistrar registrar); + } + public sealed class IntegerToNullableIntegerTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public IntegerToNullableIntegerTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(int from, object? conversionHint, out int? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class IntegerToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class IntegerToStringTypeConverter : ReactiveUI.BindingTypeConverter { public IntegerToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(int from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public class InteractionBinderImplementation : ReactiveUI.IInteractionBinderImplementation, Splat.IEnableLogger { public InteractionBinderImplementation() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.Threading.Tasks.Task> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.IObservable> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } } public static class InteractionBindingMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression binding which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression binding which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static System.IDisposable BindInteraction(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression>> propertyName, System.Func, System.Threading.Tasks.Task> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression binding which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression binding which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static System.IDisposable BindInteraction(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression>> propertyName, System.Func, System.IObservable> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } @@ -580,11 +724,20 @@ namespace ReactiveUI public System.IDisposable RegisterHandler(System.Func, System.Threading.Tasks.Task> handler) { } public System.IDisposable RegisterHandler(System.Func, System.IObservable> handler) { } } - public class LongToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class LongToNullableLongTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public LongToNullableLongTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(long from, object? conversionHint, out long? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class LongToStringTypeConverter : ReactiveUI.BindingTypeConverter { public LongToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(long from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public class MessageBus : ReactiveUI.IMessageBus, Splat.IEnableLogger { @@ -599,151 +752,199 @@ namespace ReactiveUI } public static class MutableDependencyResolverExtensions { - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification="Generic registration does not use dynamic code")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification="Generic registration does not use reflection")] public static Splat.IMutableDependencyResolver RegisterSingletonViewForViewModel(this Splat.IMutableDependencyResolver resolver, string? contract = null) where TView : class, ReactiveUI.IViewFor, new () where TViewModel : class { } - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification="Generic registration does not use dynamic code")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification="Generic registration does not use reflection")] public static Splat.IMutableDependencyResolver RegisterViewForViewModel(this Splat.IMutableDependencyResolver resolver, string? contract = null) where TView : class, ReactiveUI.IViewFor, new () where TViewModel : class { } } - public class NullableByteToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableBooleanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableBooleanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(bool? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableByteToByteTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableByteToByteTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(byte? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out byte result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableByteToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableByteToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(byte? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDateOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableDateOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateOnly? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDateTimeOffsetToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableDateTimeOffsetToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTimeOffset? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDateTimeToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableDateTimeToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTime? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDecimalToDecimalTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableDecimalToDecimalTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(decimal? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out decimal result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class NullableDecimalToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableDecimalToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableDecimalToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(decimal? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } - public class NullableDoubleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableDoubleToDoubleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableDoubleToDoubleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(double? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out double result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableDoubleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableDoubleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(double? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableGuidToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableGuidToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.Guid? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableIntegerToIntegerTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableIntegerToIntegerTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(int? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out int result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class NullableIntegerToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableIntegerToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableIntegerToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(int? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } - public class NullableLongToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableLongToLongTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableLongToLongTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(long? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out long result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableLongToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableLongToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(long? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } - public class NullableShortToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableShortToShortTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableShortToShortTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(short? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out short result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableShortToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableShortToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(short? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableSingleToSingleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableSingleToSingleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(float? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out float result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class NullableSingleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableSingleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableSingleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(float? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableTimeOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableTimeOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeOnly? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableTimeSpanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableTimeSpanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeSpan? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } public static class OAPHCreationHelperMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, out ReactiveUI.ObservableAsPropertyHelper result, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, TRet initialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, out ReactiveUI.ObservableAsPropertyHelper result, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, TRet initialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, out ReactiveUI.ObservableAsPropertyHelper result, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, out ReactiveUI.ObservableAsPropertyHelper result, TRet initialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, out ReactiveUI.ObservableAsPropertyHelper result, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } } public sealed class ObservableAsPropertyHelper : ReactiveUI.IHandleObservableErrors, Splat.IEnableLogger, System.IDisposable { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public ObservableAsPropertyHelper(System.IObservable observable, System.Action onChanged, T? initialValue = default, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public ObservableAsPropertyHelper(System.IObservable observable, System.Action onChanged, System.Action? onChanging = null, System.Func? getInitialValue = null, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public ObservableAsPropertyHelper(System.IObservable observable, System.Action onChanged, System.Action? onChanging = null, T? initialValue = default, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) { } public bool IsSubscribed { get; } public System.IObservable ThrownExceptions { get; } public T Value { get; } public void Dispose() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public static ReactiveUI.ObservableAsPropertyHelper Default(T? initialValue = default, System.Reactive.Concurrency.IScheduler? scheduler = null) { } } public static class ObservableFuncMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static System.IObservable ToObservable(this System.Linq.Expressions.Expression> expression, TSource? source, bool beforeChange = false, bool skipInitial = false) { } } public static class ObservableLoggingMixin @@ -769,24 +970,12 @@ namespace ReactiveUI } public static class ObservedChangedMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("TryGetValue uses expression chain analysis and reflection which require dynamic c" + - "ode generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TryGetValue uses expression chain analysis and reflection which may reference mem" + - "bers that could be trimmed.")] public static string GetPropertyName(this ReactiveUI.IObservedChange item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetValue uses expression chain analysis and reflection which require dynamic code" + - " generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetValue uses expression chain analysis and reflection which may reference member" + - "s that could be trimmed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static TValue GetValue(this ReactiveUI.IObservedChange item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetValueOrDefault uses expression chain analysis and reflection which require dyn" + - "amic code generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetValueOrDefault uses expression chain analysis and reflection which may referen" + - "ce members that could be trimmed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static TValue? GetValueOrDefault(this ReactiveUI.IObservedChange item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Value method uses GetValue which requires expression chain analysis and reflectio" + - "n.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Value method uses GetValue which may reference members that could be trimmed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable Value(this System.IObservable> item) { } } public static class OrderedComparer @@ -801,185 +990,104 @@ namespace ReactiveUI public static System.Collections.Generic.IComparer OrderByDescending(System.Func selector) { } public static System.Collections.Generic.IComparer OrderByDescending(System.Func selector, System.Collections.Generic.IComparer comparer) { } } - public class POCOObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger + public sealed class POCOObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public POCOObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses reflection and type analysis")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses reflection and type analysis")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } - public static class PlatformRegistrationManager - { - public static void SetRegistrationNamespaces(params ReactiveUI.RegistrationNamespace[] namespaces) { } - } public class PlatformRegistrations : ReactiveUI.IWantsToRegisterStuff { public PlatformRegistrations() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Platform registration uses ComponentModelTypeConverter and RxApp which require dy" + - "namic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Platform registration uses ComponentModelTypeConverter and RxApp which may requir" + - "e unreferenced code")] - public void Register(System.Action, System.Type> registerFunction) { } + public void Register(ReactiveUI.IRegistrar registrar) { } } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflectio" + + "n-based invocation.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types and expression graphs which may be trimmed.")] public class PropertyBinderImplementation : ReactiveUI.IPropertyBinderImplementation, Splat.IEnableLogger { public PropertyBinderImplementation() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] public ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, System.Func vmToViewConverter, System.Func viewToVmConverter, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] public ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] public System.IDisposable BindTo(System.IObservable observedChange, TTarget? target, System.Linq.Expressions.Expression> propertyExpression, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TTarget : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] public ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func selector) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] public ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflectio" + + "n-based invocation.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class PropertyBindingMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func vmToViewConverter, System.Func viewToVmConverter) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, System.Func vmToViewConverter, System.Func viewToVmConverter, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] public static System.IDisposable BindTo(this System.IObservable @this, TTarget? target, System.Linq.Expressions.Expression> property, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TTarget : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] public static ReactiveUI.IReactiveBinding OneWayBind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func selector) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] public static ReactiveUI.IReactiveBinding OneWayBind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } } public static class ReactiveCommand { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("CreateCombined uses CombinedReactiveCommand which requires dynamic code generatio" + - "n.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CreateCombined uses CombinedReactiveCommand which may require unreferenced code.")] public static ReactiveUI.CombinedReactiveCommand CreateCombined(System.Collections.Generic.IEnumerable> childCommands, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromObservable(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromObservable(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } } public abstract class ReactiveCommandBase : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveCommand, ReactiveUI.IReactiveCommand, System.IDisposable, System.IObservable, System.Windows.Input.ICommand @@ -1001,28 +1109,18 @@ namespace ReactiveUI { public static System.IDisposable InvokeCommand(this System.IObservable item, System.Windows.Input.ICommand? command) { } public static System.IDisposable InvokeCommand(this System.IObservable item, ReactiveUI.ReactiveCommandBase? command) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvokeCommand uses WhenAnyValue which requires dynamic code generation for expres" + - "sion tree analysis.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvokeCommand uses WhenAnyValue which may reference members that could be trimmed" + - ".")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable InvokeCommand(this System.IObservable item, TTarget? target, System.Linq.Expressions.Expression> commandProperty) where TTarget : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvokeCommand uses WhenAnyValue which requires dynamic code generation for expres" + - "sion tree analysis.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvokeCommand uses WhenAnyValue which may reference members that could be trimmed" + - ".")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable InvokeCommand(this System.IObservable item, TTarget? target, System.Linq.Expressions.Expression?>> commandProperty) where TTarget : class { } } public class ReactiveCommand : ReactiveUI.ReactiveCommandBase { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] protected ReactiveCommand([System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "Result", "Cancel"})] System.Func, System.Action>>> execute, System.IObservable? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] protected ReactiveCommand(System.Func> execute, System.IObservable? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { } public override System.IObservable CanExecute { get; } public override System.IObservable IsExecuting { get; } @@ -1032,54 +1130,45 @@ namespace ReactiveUI public override System.IObservable Execute(TParam parameter) { } public override System.IDisposable Subscribe(System.IObserver observer) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static class ReactiveNotifyPropertyChangedMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, bool beforeChange) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName, bool beforeChange) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, bool beforeChange, bool skipInitial) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName, bool beforeChange, bool skipInitial) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, bool beforeChange, bool skipInitial, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName, bool beforeChange, bool skipInitial, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, System.Func selector, bool beforeChange) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange, bool skipInitial) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange, bool skipInitial, bool suppressWarnings) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange, bool skipInitial, bool suppressWarnings, bool isDistinct) { } } [System.Runtime.Serialization.DataContract] @@ -1105,8 +1194,6 @@ namespace ReactiveUI public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; public bool AreChangeNotificationsEnabled() { } public System.IDisposable DelayChangeNotifications() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] public System.IDisposable SuppressChangeNotifications() { } } public class ReactivePropertyChangedEventArgs : System.ComponentModel.PropertyChangedEventArgs, ReactiveUI.IReactivePropertyChangedEventArgs @@ -1121,32 +1208,17 @@ namespace ReactiveUI } public static class ReactivePropertyMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses DataAnnotations validation which requires dynamic code generation" + - ".")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses DataAnnotations validation which may require unreferenced code.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("DataAnnotations validation uses reflection to discover attributes and is not trim" + + "-safe. Use manual validation for AOT scenarios.")] public static ReactiveUI.ReactiveProperty AddValidation(this ReactiveUI.ReactiveProperty self, System.Linq.Expressions.Expression?>> selfSelector) { } public static System.IObservable ObserveValidationErrors(this ReactiveUI.ReactiveProperty self) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses ReactiveObject and RxApp which require dynam" + - "ic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses ReactiveObject and RxApp which may require u" + - "nreferenced code")] [System.Runtime.Serialization.DataContract] public class ReactiveProperty : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty, System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable, System.Reactive.Disposables.ICancelable { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] public ReactiveProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] public ReactiveProperty(T? initialValue) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] public ReactiveProperty(T? initialValue, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses ReactiveObject and RxApp which require dynam" + - "ic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses ReactiveObject and RxApp which may require u" + - "nreferenced code")] public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { } public bool HasErrors { get; } public bool IsDisposed { get; } @@ -1156,30 +1228,16 @@ namespace ReactiveUI [System.Text.Json.Serialization.JsonInclude] public T Value { get; set; } public event System.EventHandler? ErrorsChanged; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func, System.IObservable> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func, System.IObservable> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func validator, bool ignoreInitialError = false) { } public void CheckValidation() { } public void Dispose() { } protected virtual void Dispose(bool disposing) { } public System.Collections.IEnumerable? GetErrors(string? propertyName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Refresh uses RaisePropertyChanged which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refresh uses RaisePropertyChanged which may require unreferenced code")] public void Refresh() { } public System.IDisposable Subscribe(System.IObserver observer) { } public static ReactiveUI.ReactiveProperty Create() { } @@ -1190,10 +1248,6 @@ namespace ReactiveUI [System.Runtime.Serialization.DataContract] public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveRecord constructor uses extension methods that require dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveRecord constructor uses extension methods that may require unreferenced c" + - "ode")] public ReactiveRecord() { } [System.ComponentModel.Browsable(false)] [System.ComponentModel.DataAnnotations.Display(AutoGenerateField=false, AutoGenerateFilter=false, Order=-1)] @@ -1212,72 +1266,37 @@ namespace ReactiveUI public System.IObservable ThrownExceptions { get; } public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AreChangeNotificationsEnabled uses extension methods that require dynamic code ge" + - "neration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AreChangeNotificationsEnabled uses extension methods that may require unreference" + - "d code")] public bool AreChangeNotificationsEnabled() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("DelayChangeNotifications uses extension methods that require dynamic code generat" + - "ion")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("DelayChangeNotifications uses extension methods that may require unreferenced cod" + - "e")] public System.IDisposable DelayChangeNotifications() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SuppressChangeNotifications uses extension methods that require dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SuppressChangeNotifications uses extension methods that may require unreferenced " + - "code")] public System.IDisposable SuppressChangeNotifications() { } } public static class Reflection { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression tree analysis requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression tree analysis may reference members that could be trimmed")] public static string ExpressionToPropertyNames(System.Linq.Expressions.Expression? expression) { } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event access may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Reflects over custom delegate Invoke signature; members may be trimmed.")] public static System.Type GetEventArgsTypeForEvent([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type type, string? eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member access may reference members that could be trimmed")] public static System.Func? GetValueFetcherForProperty(System.Reflection.MemberInfo? member) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member access may reference members that could be trimmed")] public static System.Func GetValueFetcherOrThrow(System.Reflection.MemberInfo? member) { } - public static System.Action GetValueSetterForProperty(System.Reflection.MemberInfo? member) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member access may reference members that could be trimmed")] - public static System.Action? GetValueSetterOrThrow(System.Reflection.MemberInfo? member) { } + public static System.Action? GetValueSetterForProperty(System.Reflection.MemberInfo? member) { } + public static System.Action GetValueSetterOrThrow(System.Reflection.MemberInfo? member) { } public static bool IsStatic(this System.Reflection.PropertyInfo item) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Resolves types by name and loads assemblies; types may be trimmed.")] public static System.Type? ReallyFindType(string? type, bool throwOnFailure) { } public static System.Linq.Expressions.Expression Rewrite(System.Linq.Expressions.Expression? expression) { } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Method access may reference members that could be trimmed")] + public static void ThrowIfMethodsNotOverloaded(string callingTypeName, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type targetType, params string[] methodsToCheck) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Inspects declared methods on a runtime type; members may be trimmed.")] public static void ThrowIfMethodsNotOverloaded(string callingTypeName, object targetObject, params string[] methodsToCheck) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TryGetAllValuesForPropertyChain(out ReactiveUI.IObservedChange[] changeValues, object? current, System.Collections.Generic.IEnumerable expressionChain) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TryGetValueForPropertyChain(out TValue changeValue, object? current, System.Collections.Generic.IEnumerable expressionChain) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TrySetValueToPropertyChain(object? target, System.Collections.Generic.IEnumerable expressionChain, TValue value, bool shouldThrow = true) { } } - public enum RegistrationNamespace - { - None = 0, - Winforms = 1, - Wpf = 2, - Uno = 3, - UnoWinUI = 4, - Blazor = 5, - Drawing = 6, - Avalonia = 7, - Maui = 8, - Uwp = 9, - WinUI = 10, - } public class Registrations : ReactiveUI.IWantsToRegisterStuff { public Registrations() { } - public void Register(System.Action, System.Type> registerFunction) { } + public void Register(ReactiveUI.IRegistrar registrar) { } } public static class RoutableViewModelMixin { @@ -1285,13 +1304,9 @@ namespace ReactiveUI public static System.IObservable WhenNavigatedToObservable(this ReactiveUI.IRoutableViewModel item) { } public static System.IObservable WhenNavigatingFromObservable(this ReactiveUI.IRoutableViewModel item) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutingState uses RxApp and ReactiveCommand which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutingState uses RxApp and ReactiveCommand which may require unreferenced code")] [System.Runtime.Serialization.DataContract] public class RoutingState : ReactiveUI.ReactiveObject { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutingState uses ReactiveCommand which requires dynamic code generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutingState uses ReactiveCommand which may require unreferenced code.")] public RoutingState(System.Reactive.Concurrency.IScheduler? scheduler = null) { } [System.Runtime.Serialization.IgnoreDataMember] [System.Text.Json.Serialization.JsonIgnore] @@ -1318,21 +1333,29 @@ namespace ReactiveUI where T : ReactiveUI.IRoutableViewModel { } public static ReactiveUI.IRoutableViewModel? GetCurrentViewModel(this ReactiveUI.RoutingState item) { } } - public static class RxApp + public static class RxCacheSize { - public const int BigCacheLimit = 256; - public const int SmallCacheLimit = 64; - public static System.IObserver DefaultExceptionHandler { get; set; } - public static System.Reactive.Concurrency.IScheduler MainThreadScheduler { get; set; } - public static bool SuppressViewCommandBindingMessage { get; set; } - public static ReactiveUI.ISuspensionHost SuspensionHost { get; set; } - public static System.Reactive.Concurrency.IScheduler TaskpoolScheduler { get; set; } + public static int BigCacheLimit { get; } + public static int SmallCacheLimit { get; } + } + public static class RxConverters + { + public static ReactiveUI.ConverterService Current { get; } } public static class RxSchedulers { public static System.Reactive.Concurrency.IScheduler MainThreadScheduler { get; set; } + public static bool SuppressViewCommandBindingMessage { get; set; } public static System.Reactive.Concurrency.IScheduler TaskpoolScheduler { get; set; } } + public static class RxState + { + public static System.IObserver DefaultExceptionHandler { get; } + } + public static class RxSuspension + { + public static ReactiveUI.ISuspensionHost SuspensionHost { get; } + } public class ScheduledSubject : System.IDisposable, System.IObservable, System.IObserver, System.Reactive.Subjects.ISubject, System.Reactive.Subjects.ISubject { public ScheduledSubject(System.Reactive.Concurrency.IScheduler scheduler, System.IObserver? defaultObserver = null, System.Reactive.Subjects.ISubject? defaultSubject = null) { } @@ -1343,42 +1366,283 @@ namespace ReactiveUI public void OnNext(T value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } - public class ShortToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class SetMethodBindingConverterRegistry + { + public SetMethodBindingConverterRegistry() { } + public System.Collections.Generic.IEnumerable GetAllConverters() { } + public void Register(ReactiveUI.ISetMethodBindingConverter converter) { } + public ReactiveUI.ISetMethodBindingConverter? TryGetConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? toType) { } + } + public sealed class ShortToNullableShortTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public ShortToNullableShortTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(short from, object? conversionHint, out short? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class ShortToStringTypeConverter : ReactiveUI.BindingTypeConverter { public ShortToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(short from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed class SingleInstanceViewAttribute : System.Attribute { public SingleInstanceViewAttribute() { } } - public class SingleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class SingleToNullableSingleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public SingleToNullableSingleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(float from, object? conversionHint, out float? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class SingleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public SingleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(float from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } - public class StringConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class StringConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger { public StringConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result) { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class StringToBooleanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToBooleanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out bool result) { } + } + public sealed class StringToByteTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToByteTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out byte result) { } + } + public sealed class StringToDateOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDateOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.DateOnly result) { } + } + public sealed class StringToDateTimeOffsetTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDateTimeOffsetTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.DateTimeOffset result) { } + } + public sealed class StringToDateTimeTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDateTimeTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.DateTime result) { } + } + public sealed class StringToDecimalTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDecimalTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out decimal result) { } + } + public sealed class StringToDoubleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDoubleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out double result) { } + } + public sealed class StringToGuidTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToGuidTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Guid result) { } + } + public sealed class StringToIntegerTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToIntegerTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out int result) { } + } + public sealed class StringToLongTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToLongTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out long result) { } + } + public sealed class StringToNullableBooleanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableBooleanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out bool? result) { } + } + public sealed class StringToNullableByteTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableByteTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out byte? result) { } + } + public sealed class StringToNullableDateOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDateOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.DateOnly? result) { } + } + public sealed class StringToNullableDateTimeOffsetTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDateTimeOffsetTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.DateTimeOffset? result) { } + } + public sealed class StringToNullableDateTimeTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDateTimeTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.DateTime? result) { } + } + public sealed class StringToNullableDecimalTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDecimalTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out decimal? result) { } + } + public sealed class StringToNullableDoubleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDoubleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out double? result) { } + } + public sealed class StringToNullableGuidTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableGuidTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.Guid? result) { } + } + public sealed class StringToNullableIntegerTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableIntegerTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out int? result) { } + } + public sealed class StringToNullableLongTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableLongTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out long? result) { } + } + public sealed class StringToNullableShortTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableShortTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out short? result) { } + } + public sealed class StringToNullableSingleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableSingleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out float? result) { } + } + public sealed class StringToNullableTimeOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableTimeOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.TimeOnly? result) { } + } + public sealed class StringToNullableTimeSpanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableTimeSpanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.TimeSpan? result) { } + } + public sealed class StringToShortTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToShortTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out short result) { } + } + public sealed class StringToSingleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToSingleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out float result) { } + } + public sealed class StringToTimeOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToTimeOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.TimeOnly result) { } + } + public sealed class StringToTimeSpanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToTimeSpanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.TimeSpan result) { } + } + public sealed class StringToUriTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToUriTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Uri? result) { } } public static class SuspensionHostExtensions { + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. Prefer GetAppState(ISuspensionHost) used with SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. Prefer GetAppState(ISuspensionHost) used with SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] public static T GetAppState(this ReactiveUI.ISuspensionHost item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObserveAppState uses WhenAny which requires dynamic code generation for expressio" + - "n tree analysis")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObserveAppState uses WhenAny which may reference members that could be trimmed")] + public static TAppState GetAppState(this ReactiveUI.Interfaces.ISuspensionHost item) + where TAppState : class { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This overload uses WhenAny, which can require unreferenced/dynamic code in trimmi" + + "ng/AOT scenarios. Prefer ObserveAppState(ISuspensionHost) " + + "for trimming/AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This overload uses WhenAny, which can require unreferenced/dynamic code in trimmi" + + "ng/AOT scenarios. Prefer ObserveAppState(ISuspensionHost) " + + "for trimming/AOT scenarios.")] public static System.IObservable ObserveAppState(this ReactiveUI.ISuspensionHost item) where T : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SetupDefaultSuspendResume uses ISuspensionDriver which may require dynamic code g" + - "eneration for serialization")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SetupDefaultSuspendResume uses ISuspensionDriver which may require unreferenced c" + - "ode for serialization")] + public static System.IObservable ObserveAppState(this ReactiveUI.Interfaces.ISuspensionHost item) + where TAppState : class { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This overload may invoke ISuspensionDriver.LoadState()/SaveState(T), which are" + + " commonly reflection-based. Prefer SetupDefaultSuspendResume(..., Jso" + + "nTypeInfo, ...) for trimming/AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This overload may invoke ISuspensionDriver.LoadState()/SaveState(T), which are" + + " commonly reflection-based. Prefer SetupDefaultSuspendResume(..., Jso" + + "nTypeInfo, ...) for trimming/AOT scenarios.")] public static System.IDisposable SetupDefaultSuspendResume(this ReactiveUI.ISuspensionHost item, ReactiveUI.ISuspensionDriver? driver = null) { } + public static System.IDisposable SetupDefaultSuspendResume(this ReactiveUI.Interfaces.ISuspensionHost item, System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo, ReactiveUI.ISuspensionDriver? driver = null) + where TAppState : class { } + } + public class SuspensionHost : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveObject, ReactiveUI.ISuspensionHost, ReactiveUI.Interfaces.ISuspensionHost, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IDisposable + { + public SuspensionHost() { } + public TAppState AppStateValue { get; set; } + public System.IObservable AppStateValueChanged { get; } + public System.Func? CreateNewAppStateTyped { get; set; } + public System.IObservable IsContinuing { get; set; } + public System.IObservable IsLaunchingNew { get; set; } + public System.IObservable IsResuming { get; set; } + public System.IObservable IsUnpausing { get; set; } + public System.IObservable ShouldInvalidateState { get; set; } + public System.IObservable ShouldPersistState { get; set; } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + } + public sealed class TimeOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public TimeOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeOnly from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class TimeSpanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public TimeSpanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeSpan from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public enum TriggerUpdate { @@ -1400,6 +1664,12 @@ namespace ReactiveUI public TInput Input { get; } public ReactiveUI.Interaction? Interaction { get; } } + public sealed class UriToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public UriToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.Uri? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed class ViewContractAttribute : System.Attribute { @@ -1408,23 +1678,18 @@ namespace ReactiveUI } public static class ViewForMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Action> block) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Func> block) { } public static void WhenActivated(this ReactiveUI.IActivatableViewModel item, System.Action> block) { } public static void WhenActivated(this ReactiveUI.IActivatableViewModel item, System.Action block) { } public static void WhenActivated(this ReactiveUI.IActivatableViewModel item, System.Func> block) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Action> block, ReactiveUI.IViewFor view) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Action block, ReactiveUI.IViewFor? view = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Func> block, ReactiveUI.IViewFor? view) { } } public static class ViewLocator @@ -1437,6 +1702,18 @@ namespace ReactiveUI public ViewLocatorNotFoundException(string message) { } public ViewLocatorNotFoundException(string message, System.Exception innerException) { } } + public sealed class ViewMappingBuilder + { + public ReactiveUI.ViewMappingBuilder Map(string? contract = null) + where TViewModel : class + where TView : class, ReactiveUI.IViewFor, new () { } + public ReactiveUI.ViewMappingBuilder Map(System.Func factory, string? contract = null) + where TViewModel : class + where TView : class, ReactiveUI.IViewFor { } + public ReactiveUI.ViewMappingBuilder MapFromServiceLocator(string? contract = null) + where TViewModel : class + where TView : class, ReactiveUI.IViewFor { } + } public sealed class ViewModelActivator : System.IDisposable { public ViewModelActivator() { } @@ -1454,716 +1731,205 @@ namespace ReactiveUI public System.IDisposable Schedule(TState state, System.DateTimeOffset dueTime, System.Func action) { } public System.IDisposable Schedule(TState state, System.TimeSpan dueTime, System.Func action) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class WhenAnyMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, System.Func, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, System.Func, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, System.Func, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, System.Func, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Func, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Func, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Func, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Func, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Linq.Expressions.Expression? property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Linq.Expressions.Expression? property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string propertyName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string propertyName, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func selector, bool isDistinct) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class WhenAnyObservableMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11, System.Linq.Expressions.Expression?>> obs12) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11, System.Linq.Expressions.Expression?>> obs12, System.Func selector) where TSender : class { } } @@ -2172,6 +1938,7 @@ namespace ReactiveUI.Builder { public static class BuilderMixins { + public static ReactiveUI.Builder.IReactiveUIBuilder BuildApp(this Splat.Builder.IAppBuilder appBuilder) { } public static ReactiveUI.Builder.IReactiveUIBuilder ConfigureMessageBus(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action configure) { } public static ReactiveUI.Builder.IReactiveUIBuilder ConfigureSuspensionDriver(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action configure) { } public static ReactiveUI.Builder.IReactiveUIBuilder ConfigureViewLocator(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action configure) { } @@ -2187,9 +1954,18 @@ namespace ReactiveUI.Builder where TViewModel : class, ReactiveUI.IReactiveObject { } public static ReactiveUI.Builder.IReactiveUIBuilder RegisterViewModel(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder) where TViewModel : class, ReactiveUI.IReactiveObject, new () { } + public static ReactiveUI.Builder.IReactiveUIBuilder RegisterViews(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Action configure) { } public static ReactiveUI.Builder.IReactiveUIBuilder UsingSplatBuilder(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action? appBuilder) { } public static ReactiveUI.Builder.IReactiveUIBuilder UsingSplatModule(this ReactiveUI.Builder.IReactiveUIBuilder builder, T registrationModule) where T : Splat.Builder.IModule { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.IBindingTypeConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func factory) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.BindingTypeConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func> factory) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverters(this ReactiveUI.Builder.IReactiveUIBuilder builder, params ReactiveUI.IBindingTypeConverter[] converters) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConvertersFrom(this ReactiveUI.Builder.IReactiveUIBuilder builder, Splat.IReadonlyDependencyResolver resolver) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.IBindingFallbackConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func factory) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } @@ -2207,15 +1983,18 @@ namespace ReactiveUI.Builder public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithMainThreadScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + public static ReactiveUI.Builder.IReactiveUIBuilder WithMessageBus(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, ReactiveUI.IMessageBus messageBus) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithPlatformModule(this ReactiveUI.Builder.IReactiveUIBuilder builder) where T : ReactiveUI.IWantsToRegisterStuff, new () { } public static ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Action configureAction) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithRegistrationOnBuild(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Action configureAction) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.ISetMethodBindingConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func factory) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithTaskPoolScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + public static ReactiveUI.Builder.IReactiveUIBuilder WithViewModule(this ReactiveUI.Builder.IReactiveUIBuilder builder) + where TModule : ReactiveUI.IViewModule, new () { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] public static ReactiveUI.Builder.IReactiveUIBuilder WithViewsFromAssembly(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Reflection.Assembly assembly) { } } public interface IReactiveUIBuilder : Splat.Builder.IAppBuilder @@ -2238,6 +2017,8 @@ namespace ReactiveUI.Builder where TViewModel : class, ReactiveUI.IReactiveObject, new (); ReactiveUI.Builder.IReactiveUIBuilder UsingSplatModule(T registrationModule) where T : Splat.Builder.IModule; + ReactiveUI.Builder.IReactiveUIBuilder WithCacheSizes(int smallCacheLimit, int bigCacheLimit); + ReactiveUI.Builder.IReactiveUIBuilder WithExceptionHandler(System.IObserver exceptionHandler); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); @@ -2255,19 +2036,17 @@ namespace ReactiveUI.Builder ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIBuilder WithMainThreadScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + ReactiveUI.Builder.IReactiveUIBuilder WithMessageBus(ReactiveUI.IMessageBus messageBus); ReactiveUI.Builder.IReactiveUIBuilder WithPlatformModule() where T : ReactiveUI.IWantsToRegisterStuff, new (); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ProcessRegistrationForNamespace uses reflection to locate types which may be trim" + - "med.")] ReactiveUI.Builder.IReactiveUIBuilder WithPlatformServices(); ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(System.Action configureAction); ReactiveUI.Builder.IReactiveUIBuilder WithRegistrationOnBuild(System.Action configureAction); + ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost(); + ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost(); ReactiveUI.Builder.IReactiveUIBuilder WithTaskPoolScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] ReactiveUI.Builder.IReactiveUIBuilder WithViewsFromAssembly(System.Reflection.Assembly assembly); } public interface IReactiveUIInstance : Splat.Builder.IAppInstance @@ -2278,6 +2057,7 @@ namespace ReactiveUI.Builder public sealed class ReactiveUIBuilder : Splat.Builder.AppBuilder, ReactiveUI.Builder.IReactiveUIBuilder, ReactiveUI.Builder.IReactiveUIInstance, Splat.Builder.IAppBuilder, Splat.Builder.IAppInstance { public ReactiveUIBuilder(Splat.IMutableDependencyResolver resolver, Splat.IReadonlyDependencyResolver? current) { } + public ReactiveUI.ConverterService ConverterService { get; } public System.Reactive.Concurrency.IScheduler? MainThreadScheduler { get; } public System.Reactive.Concurrency.IScheduler? TaskpoolScheduler { get; } public ReactiveUI.Builder.IReactiveUIInstance BuildApp() { } @@ -2299,9 +2079,16 @@ namespace ReactiveUI.Builder public ReactiveUI.Builder.IReactiveUIBuilder UsingSplatBuilder(System.Action appBuilder) { } public ReactiveUI.Builder.IReactiveUIBuilder UsingSplatModule(T registrationModule) where T : Splat.Builder.IModule { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] + public ReactiveUI.Builder.IReactiveUIBuilder WithCacheSizes(int smallCacheLimit, int bigCacheLimit) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(ReactiveUI.IBindingTypeConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(System.Func factory) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(ReactiveUI.BindingTypeConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(System.Func> factory) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConvertersFrom(Splat.IReadonlyDependencyResolver resolver) { } public override Splat.Builder.IAppBuilder WithCoreServices() { } + public ReactiveUI.Builder.IReactiveUIBuilder WithExceptionHandler(System.IObserver exceptionHandler) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(ReactiveUI.IBindingFallbackConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(System.Func factory) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } @@ -2319,23 +2106,35 @@ namespace ReactiveUI.Builder public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIBuilder WithMainThreadScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + public ReactiveUI.Builder.IReactiveUIBuilder WithMessageBus(ReactiveUI.IMessageBus messageBus) { } public ReactiveUI.Builder.IReactiveUIBuilder WithPlatformModule() where T : ReactiveUI.IWantsToRegisterStuff, new () { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] public ReactiveUI.Builder.IReactiveUIBuilder WithPlatformServices() { } + public ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(ReactiveUI.IWantsToRegisterStuff registration) { } public ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(System.Action configureAction) { } public ReactiveUI.Builder.IReactiveUIBuilder WithRegistrationOnBuild(System.Action configureAction) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(ReactiveUI.ISetMethodBindingConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(System.Func factory) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost() { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost() { } public ReactiveUI.Builder.IReactiveUIBuilder WithTaskPoolScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] public ReactiveUI.Builder.IReactiveUIBuilder WithViewsFromAssembly(System.Reflection.Assembly assembly) { } } public static class RxAppBuilder { public static ReactiveUI.Builder.ReactiveUIBuilder CreateReactiveUIBuilder() { } public static ReactiveUI.Builder.ReactiveUIBuilder CreateReactiveUIBuilder(this Splat.IMutableDependencyResolver resolver) { } + public static void EnsureInitialized() { } + } +} +namespace ReactiveUI.Interfaces +{ + public interface ISuspensionHost : ReactiveUI.IReactiveObject, ReactiveUI.ISuspensionHost, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging + { + TAppState AppStateValue { get; set; } + System.IObservable AppStateValueChanged { get; } + System.Func? CreateNewAppStateTyped { get; set; } } } \ No newline at end of file diff --git a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet9_0.verified.txt b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet9_0.verified.txt index a45b0b4dfd..27c8f9b9a5 100644 --- a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet9_0.verified.txt +++ b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet9_0.verified.txt @@ -7,18 +7,22 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Builder.WpfApp")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Drawing")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Maui")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Maui.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Test.Utilities")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.TestGuiMocks")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Testing")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Testing.Reactive")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Uno")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Uno.WinUI")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.WinForms.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.WinUI")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Winforms")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Wpf")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Wpf.Tests")] namespace ReactiveUI { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static class AutoPersistHelper { public static System.IDisposable ActOnEveryObject(this System.Collections.ObjectModel.ObservableCollection @this, System.Action onAdd, System.Action onRemove) @@ -30,19 +34,69 @@ namespace ReactiveUI public static System.IDisposable ActOnEveryObject(this TCollection collection, System.Action onAdd, System.Action onRemove) where TItem : ReactiveUI.IReactiveObject where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } - public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, System.TimeSpan? interval = default) + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + public static System.IDisposable AutoPersist<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(this T @this, System.Func> doPersist, System.TimeSpan? interval = default) where T : ReactiveUI.IReactiveObject { } - public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) + public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) where T : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AutoPersist may reflect over the runtime type when it differs from T. In trimmed/" + + "AOT builds, required property/attribute metadata may be removed unless explicitl" + + "y preserved. Prefer the overloads that accept AutoPersistMetadata to avoid runti" + + "me reflection.")] + public static System.IDisposable AutoPersist<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TDontCare>(this T @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) + where T : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersist(this T @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where T : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ReadOnlyObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject { } + public static System.IDisposable AutoPersistCollection(this System.Collections.ObjectModel.ReadOnlyObservableCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"AutoPersistCollection may reflect over runtime item types via AutoPersist when generic type parameters do not match item runtime types. In trimmed/AOT builds, required property/attribute metadata may be removed unless explicitly preserved. Prefer the overloads that accept AutoPersistMetadata or a metadata provider to avoid runtime reflection.")] public static System.IDisposable AutoPersistCollection(this TCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.TimeSpan? interval = default) where TItem : ReactiveUI.IReactiveObject where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IDisposable AutoPersistCollection(this TCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, ReactiveUI.AutoPersistHelper.AutoPersistMetadata metadata, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IDisposable AutoPersistCollection(this TCollection @this, System.Func> doPersist, System.IObservable manualSaveSignal, System.Func metadataProvider, System.TimeSpan? interval = default) + where TItem : ReactiveUI.IReactiveObject + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static ReactiveUI.AutoPersistHelper.AutoPersistMetadata CreateMetadata<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] T>() + where T : ReactiveUI.IReactiveObject { } + public static System.Func CreateMetadataProvider<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties)] TItem>() + where TItem : ReactiveUI.IReactiveObject { } + public sealed class AutoPersistMetadata + { + public AutoPersistMetadata(bool hasDataContract, System.Collections.Generic.ISet persistablePropertyNames) { } + public bool HasDataContract { get; } + public System.Collections.Generic.ISet PersistablePropertyNames { get; } + } } public enum BindingDirection { @@ -50,17 +104,53 @@ namespace ReactiveUI TwoWay = 1, AsyncOneWay = 2, } - public class ByteToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class BindingFallbackConverterRegistry + { + public BindingFallbackConverterRegistry() { } + public System.Collections.Generic.IEnumerable GetAllConverters() { } + public void Register(ReactiveUI.IBindingFallbackConverter converter) { } + public ReactiveUI.IBindingFallbackConverter? TryGetConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType) { } + } + public sealed class BindingTypeConverterRegistry + { + public BindingTypeConverterRegistry() { } + public System.Collections.Generic.IEnumerable GetAllConverters() { } + public void Register(ReactiveUI.IBindingTypeConverter converter) { } + public ReactiveUI.IBindingTypeConverter? TryGetConverter(System.Type fromType, System.Type toType) { } + } + public abstract class BindingTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + protected BindingTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public abstract int GetAffinityForObjects(); + public abstract bool TryConvert(TFrom? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out TTo? result); + public bool TryConvertTyped(object? from, object? conversionHint, out object? result) { } + } + public sealed class BooleanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public BooleanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(bool from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class ByteToNullableByteTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public ByteToNullableByteTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(byte from, object? conversionHint, out byte? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class ByteToStringTypeConverter : ReactiveUI.BindingTypeConverter { public ByteToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(byte from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public class CanActivateViewFetcher : ReactiveUI.IActivationForViewFetcher { public CanActivateViewFetcher() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } public int GetAffinityForView(System.Type view) { } } @@ -73,10 +163,6 @@ namespace ReactiveUI } public class CombinedReactiveCommand : ReactiveUI.ReactiveCommandBase> { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("CombinedReactiveCommand uses RxApp and ReactiveCommand which require dynamic code" + - " generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CombinedReactiveCommand uses RxApp and ReactiveCommand which may require unrefere" + - "nced code.")] protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable> childCommands, System.IObservable? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } public override System.IObservable CanExecute { get; } public override System.IObservable IsExecuting { get; } @@ -88,40 +174,40 @@ namespace ReactiveUI } public static class CommandBinder { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, string? toEvent = null) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) + where TProp : System.Windows.Input.ICommand + where TControl : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) + where TProp : System.Windows.Input.ICommand + where TControl : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } + where TProp : System.Windows.Input.ICommand + where TControl : class { } } public class CommandBinderImplementation : Splat.IEnableLogger { public CommandBinderImplementation() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) + where TProp : System.Windows.Input.ICommand + where TControl : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class - where TProp : System.Windows.Input.ICommand { } + where TProp : System.Windows.Input.ICommand + where TControl : class { } } public static class ComparerChainingExtensions { @@ -130,98 +216,156 @@ namespace ReactiveUI public static System.Collections.Generic.IComparer ThenByDescending(this System.Collections.Generic.IComparer? parent, System.Func selector) { } public static System.Collections.Generic.IComparer ThenByDescending(this System.Collections.Generic.IComparer? parent, System.Func selector, System.Collections.Generic.IComparer comparer) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Component model type conversion uses reflection and dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Component model type conversion may reference types that could be trimmed")] - public class ComponentModelTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class ComponentModelFallbackConverter : ReactiveUI.IBindingFallbackConverter, Splat.IEnableLogger + { + public ComponentModelFallbackConverter() { } + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification="The callers of this method ensure getting the converter is trim compatible - i.e." + + " the type is not Nullable.")] + public int GetAffinityForObjects([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType) { } + public bool TryConvert([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, object from, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public static class ConverterMigrationHelper { - public ComponentModelTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result) { } + [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { + "TypedConverters", + "FallbackConverters", + "SetMethodConverters"})] + public static System.ValueTuple, System.Collections.Generic.IList, System.Collections.Generic.IList> ExtractConverters(Splat.IReadonlyDependencyResolver resolver) { } + public static void ImportFrom(this ReactiveUI.ConverterService converterService, Splat.IReadonlyDependencyResolver resolver) { } + } + public sealed class ConverterService + { + public ConverterService() { } + public ReactiveUI.BindingFallbackConverterRegistry FallbackConverters { get; } + public ReactiveUI.SetMethodBindingConverterRegistry SetMethodConverters { get; } + public ReactiveUI.BindingTypeConverterRegistry TypedConverters { get; } + public object? ResolveConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType) { } + public ReactiveUI.ISetMethodBindingConverter? ResolveSetMethodConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? toType) { } } - public class CreatesCommandBindingViaCommandParameter : ReactiveUI.ICreatesCommandBinding + public sealed class CreatesCommandBindingViaCommandParameter : ReactiveUI.ICreatesCommandBinding { public CreatesCommandBindingViaCommandParameter() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindCommandToObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindCommandToObject uses methods that may require unreferenced code")] - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindCommandToObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindCommandToObject uses methods that may require unreferenced code")] - public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Property access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Property access may reference members that could be trimmed")] - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs { } public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } } - public class CreatesCommandBindingViaEvent : ReactiveUI.ICreatesCommandBinding + public sealed class CreatesCommandBindingViaEvent : ReactiveUI.ICreatesCommandBinding { public CreatesCommandBindingViaEvent() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Event binding requires dynamic code generation and reflection")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Event binding requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] - public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Event binding requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event binding may reference members that could be trimmed")] - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class { } + public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action addHandler, System.Action removeHandler) + where T : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs { } public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } } - public class DecimalToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class DateOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public DateOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateOnly from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class DateTimeOffsetToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public DateTimeOffsetToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTimeOffset from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class DateTimeToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public DateTimeToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTime from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class DecimalToNullableDecimalTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public DecimalToNullableDecimalTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(decimal from, object? conversionHint, out decimal? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class DecimalToStringTypeConverter : ReactiveUI.BindingTypeConverter { public DecimalToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(decimal from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public sealed class DefaultViewLocator : ReactiveUI.IViewLocator, Splat.IEnableLogger { - public System.Func ViewModelToViewFunc { get; set; } - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification="Mapping does not use dynamic code")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification="Mapping does not use reflection")] public ReactiveUI.DefaultViewLocator Map(System.Func factory, string? contract = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("View resolution uses reflection and type discovery")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("View resolution may reference types that could be trimmed")] - public ReactiveUI.IViewFor? ResolveView(T? viewModel, string? contract = null) { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMe" + + "mbersAttribute, or generic constraints), trimming can\'t validate that the requir" + + "ements of those annotations are met.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to determine the view model type at runtime, which ma" + + "y be incompatible with trimming.")] + public ReactiveUI.IViewFor? ResolveView(object? instance, string? contract = null) { } + public ReactiveUI.IViewFor? ResolveView(string? contract = null) + where TViewModel : class { } public ReactiveUI.DefaultViewLocator Unmap(string? contract = null) where TViewModel : class { } } public static class DependencyResolverMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InitializeReactiveUI uses reflection to locate types which may be trimmed.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InitializeReactiveUI uses reflection to locate types which may be trimmed.")] - public static void InitializeReactiveUI(this Splat.IMutableDependencyResolver resolver, params ReactiveUI.RegistrationNamespace[] registrationNamespaces) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RegisterViewsForViewModels scans the provided assembly and creates instances via " + - "reflection; this is not compatible with AOT.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RegisterViewsForViewModels uses reflection over types and members which may be tr" + - "immed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to register views explicitly.")] public static void RegisterViewsForViewModels(this Splat.IMutableDependencyResolver resolver, System.Reflection.Assembly assembly) { } } - public class DoubleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class DoubleToNullableDoubleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public DoubleToNullableDoubleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(double from, object? conversionHint, out double? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class DoubleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public DoubleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(double from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } - public class DummySuspensionDriver : ReactiveUI.ISuspensionDriver + public sealed class DummySuspensionDriver : ReactiveUI.ISuspensionDriver { public DummySuspensionDriver() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvalidateState uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvalidateState uses methods that may require unreferenced code")] public System.IObservable InvalidateState() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("LoadState uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("LoadState uses methods that may require unreferenced code")] - public System.IObservable LoadState() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SaveState uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SaveState uses methods that may require unreferenced code")] - public System.IObservable SaveState(object state) { } - } - public class EqualityTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] + public System.IObservable LoadState() { } + public System.IObservable LoadState(System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo) { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + public System.IObservable SaveState(T state) { } + public System.IObservable SaveState(T state, System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo) { } + } + public sealed class EqualityTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger { public EqualityTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result) { } - public static object? DoReferenceCast(object? from, System.Type targetType) { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=false, Inherited=false)] public sealed class ExcludeFromViewRegistrationAttribute : System.Attribute @@ -231,16 +375,16 @@ namespace ReactiveUI public static class ExpressionMixins { public static object?[]? GetArgumentsArray(this System.Linq.Expressions.Expression expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression chain analysis requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression chain analysis may reference members that could be trimmed")] public static System.Collections.Generic.IEnumerable GetExpressionChain(this System.Linq.Expressions.Expression expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member info access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member info access may reference members that could be trimmed")] public static System.Reflection.MemberInfo? GetMemberInfo(this System.Linq.Expressions.Expression expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression analysis requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression analysis may reference members that could be trimmed")] public static System.Linq.Expressions.Expression? GetParent(this System.Linq.Expressions.Expression expression) { } } + public sealed class GuidToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public GuidToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.Guid from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } public interface IActivatableView { } public interface IActivatableViewModel { @@ -248,15 +392,24 @@ namespace ReactiveUI } public interface IActivationForViewFetcher { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] System.IObservable GetActivationForView(ReactiveUI.IActivatableView view); int GetAffinityForView(System.Type view); } + public interface IBindingFallbackConverter : Splat.IEnableLogger + { + int GetAffinityForObjects([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType); + bool TryConvert([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type fromType, object from, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type toType, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result); + } public interface IBindingTypeConverter : Splat.IEnableLogger { - int GetAffinityForObjects(System.Type fromType, System.Type toType); - bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result); + System.Type FromType { get; } + System.Type ToType { get; } + int GetAffinityForObjects(); + bool TryConvertTyped(object? from, object? conversionHint, out object? result); + } + public interface IBindingTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + bool TryConvert(TFrom? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out TTo? result); } public interface ICanActivate { @@ -272,24 +425,22 @@ namespace ReactiveUI } public interface ICreatesCommandBinding { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] - System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] - System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] - int GetAffinityForObject(System.Type type, bool hasEventTarget); + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class; + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class; + System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs; int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget); } public interface ICreatesObservableForProperty : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false); } public interface IHandleObservableErrors @@ -298,13 +449,11 @@ namespace ReactiveUI } public interface IInteractionBinderImplementation : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression evaluation which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression evaluation which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.Threading.Tasks.Task> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression evaluation which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression evaluation which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.IObservable> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor; @@ -334,11 +483,9 @@ namespace ReactiveUI public class INPCObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public INPCObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } public interface IObservedChange @@ -357,51 +504,37 @@ namespace ReactiveUI } public interface IPropertyBinderImplementation : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Bind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Bind uses expression trees which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, System.Func vmToViewConverter, System.Func viewToVmConverter, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Bind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Bind uses expression trees which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindTo uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindTo uses expression trees which may require unreferenced code")] System.IDisposable BindTo(System.IObservable observedChange, TTarget? target, System.Linq.Expressions.Expression> propertyExpression, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TTarget : class; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("OneWayBind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("OneWayBind uses expression trees which may require unreferenced code")] ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func selector) where TViewModel : class where TView : class, ReactiveUI.IViewFor; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("OneWayBind uses expression trees which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("OneWayBind uses expression trees which may require unreferenced code")] ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor; } public interface IPropertyBindingHook { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction); } - public class IROObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger + public sealed class IROObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public IROObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } public interface IReactiveBinding : System.IDisposable @@ -427,10 +560,6 @@ namespace ReactiveUI { System.IObservable> Changed { get; } System.IObservable> Changing { get; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SuppressChangeNotifications uses extension methods that require dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SuppressChangeNotifications uses extension methods that may require unreferenced " + - "code")] System.IDisposable SuppressChangeNotifications(); } public interface IReactiveObject : Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging @@ -461,10 +590,17 @@ namespace ReactiveUI System.IObservable ObserveErrorChanged { get; } System.IObservable ObserveHasErrors { get; } T Value { get; set; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Refresh uses RaisePropertyChanged which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refresh uses RaisePropertyChanged which may require unreferenced code")] void Refresh(); } + public interface IRegistrar + { + void Register(System.Func factory, string? contract = null) + where TService : class; + void RegisterConstant(System.Func factory, string? contract = null) + where TService : class; + void RegisterLazySingleton<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TService>(System.Func factory, string? contract = null) + where TService : class; + } public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { ReactiveUI.IScreen HostScreen { get; } @@ -476,26 +612,24 @@ namespace ReactiveUI } public interface ISetMethodBindingConverter : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] int GetAffinityForObjects(System.Type? fromType, System.Type? toType); object? PerformSet(object? toTarget, object? newValue, object?[]? arguments); } public interface ISuspensionDriver { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvalidateState uses JsonSerializer which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvalidateState uses JsonSerializer which may require unreferenced code")] System.IObservable InvalidateState(); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("LoadState implementations may use serialization which requires dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("LoadState implementations may use serialization which may require unreferenced co" + - "de")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer LoadState(" + + "JsonTypeInfo) for trimming or AOT scenarios.")] System.IObservable LoadState(); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SaveState implementations may use serialization which requires dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SaveState implementations may use serialization which may require unreferenced co" + - "de")] - System.IObservable SaveState(object state); + System.IObservable LoadState(System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo); + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Implementations commonly use reflection-based serialization. Prefer SaveState(" + + "T, JsonTypeInfo) for trimming or AOT scenarios.")] + System.IObservable SaveState(T state); + System.IObservable SaveState(T state, System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo); } public interface ISuspensionHost : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { @@ -518,47 +652,57 @@ namespace ReactiveUI } public interface IViewLocator : Splat.IEnableLogger { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ResolveView uses reflection and type discovery which require dynamic code generat" + - "ion")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ResolveView uses reflection and type discovery which may require unreferenced cod" + - "e")] - ReactiveUI.IViewFor? ResolveView(T? viewModel, string? contract = null); + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMe" + + "mbersAttribute, or generic constraints), trimming can\'t validate that the requir" + + "ements of those annotations are met.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to determine the view model type at runtime, which ma" + + "y be incompatible with trimming.")] + ReactiveUI.IViewFor? ResolveView(object? instance, string? contract = null); + ReactiveUI.IViewFor? ResolveView(string? contract = null) + where TViewModel : class; + } + public interface IViewModule + { + void RegisterViews(ReactiveUI.DefaultViewLocator locator); } public interface IWantsToRegisterStuff { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Uses reflection to create instances of types.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection to create instances of types.")] - void Register(System.Action, System.Type> registerFunction); + void Register(ReactiveUI.IRegistrar registrar); + } + public sealed class IntegerToNullableIntegerTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public IntegerToNullableIntegerTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(int from, object? conversionHint, out int? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class IntegerToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class IntegerToStringTypeConverter : ReactiveUI.BindingTypeConverter { public IntegerToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(int from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public class InteractionBinderImplementation : ReactiveUI.IInteractionBinderImplementation, Splat.IEnableLogger { public InteractionBinderImplementation() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.Threading.Tasks.Task> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public System.IDisposable BindInteraction(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression>> propertyName, System.Func, System.IObservable> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } } public static class InteractionBindingMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression binding which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression binding which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static System.IDisposable BindInteraction(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression>> propertyName, System.Func, System.Threading.Tasks.Task> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("BindInteraction uses expression binding which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BindInteraction uses expression binding which may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static System.IDisposable BindInteraction(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression>> propertyName, System.Func, System.IObservable> handler) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } @@ -580,11 +724,20 @@ namespace ReactiveUI public System.IDisposable RegisterHandler(System.Func, System.Threading.Tasks.Task> handler) { } public System.IDisposable RegisterHandler(System.Func, System.IObservable> handler) { } } - public class LongToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class LongToNullableLongTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public LongToNullableLongTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(long from, object? conversionHint, out long? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class LongToStringTypeConverter : ReactiveUI.BindingTypeConverter { public LongToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(long from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public class MessageBus : ReactiveUI.IMessageBus, Splat.IEnableLogger { @@ -599,151 +752,199 @@ namespace ReactiveUI } public static class MutableDependencyResolverExtensions { - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification="Generic registration does not use dynamic code")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification="Generic registration does not use reflection")] public static Splat.IMutableDependencyResolver RegisterSingletonViewForViewModel(this Splat.IMutableDependencyResolver resolver, string? contract = null) where TView : class, ReactiveUI.IViewFor, new () where TViewModel : class { } - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification="Generic registration does not use dynamic code")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification="Generic registration does not use reflection")] public static Splat.IMutableDependencyResolver RegisterViewForViewModel(this Splat.IMutableDependencyResolver resolver, string? contract = null) where TView : class, ReactiveUI.IViewFor, new () where TViewModel : class { } } - public class NullableByteToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableBooleanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableBooleanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(bool? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableByteToByteTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableByteToByteTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(byte? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out byte result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableByteToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableByteToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(byte? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDateOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableDateOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateOnly? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDateTimeOffsetToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableDateTimeOffsetToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTimeOffset? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDateTimeToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableDateTimeToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.DateTime? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableDecimalToDecimalTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableDecimalToDecimalTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(decimal? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out decimal result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class NullableDecimalToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableDecimalToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableDecimalToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(decimal? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } - public class NullableDoubleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableDoubleToDoubleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableDoubleToDoubleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(double? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out double result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableDoubleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableDoubleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(double? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableGuidToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableGuidToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.Guid? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableIntegerToIntegerTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableIntegerToIntegerTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(int? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out int result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class NullableIntegerToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableIntegerToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableIntegerToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(int? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } - public class NullableLongToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableLongToLongTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableLongToLongTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(long? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out long result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableLongToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableLongToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(long? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } - public class NullableShortToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableShortToShortTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableShortToShortTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(short? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out short result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class NullableShortToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableShortToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(short? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableSingleToSingleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public NullableSingleToSingleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(float? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out float result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } } - public class NullableSingleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class NullableSingleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public NullableSingleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(float? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableTimeOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableTimeOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeOnly? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } + } + public sealed class NullableTimeSpanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public NullableTimeSpanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeSpan? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out string? result) { } } public static class OAPHCreationHelperMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, out ReactiveUI.ObservableAsPropertyHelper result, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, TRet initialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, out ReactiveUI.ObservableAsPropertyHelper result, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, TRet initialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, out ReactiveUI.ObservableAsPropertyHelper result, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, System.Linq.Expressions.Expression> property, out ReactiveUI.ObservableAsPropertyHelper result, TRet initialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableToProperty uses RaisingPropertyChanged which requires dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableToProperty uses RaisingPropertyChanged which may require unreferenced c" + - "ode")] public static ReactiveUI.ObservableAsPropertyHelper ToProperty(this System.IObservable target, TObj source, string property, out ReactiveUI.ObservableAsPropertyHelper result, System.Func getInitialValue, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) where TObj : class, ReactiveUI.IReactiveObject { } } public sealed class ObservableAsPropertyHelper : ReactiveUI.IHandleObservableErrors, Splat.IEnableLogger, System.IDisposable { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public ObservableAsPropertyHelper(System.IObservable observable, System.Action onChanged, T? initialValue = default, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public ObservableAsPropertyHelper(System.IObservable observable, System.Action onChanged, System.Action? onChanging = null, System.Func? getInitialValue = null, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public ObservableAsPropertyHelper(System.IObservable observable, System.Action onChanged, System.Action? onChanging = null, T? initialValue = default, bool deferSubscription = false, System.Reactive.Concurrency.IScheduler? scheduler = null) { } public bool IsSubscribed { get; } public System.IObservable ThrownExceptions { get; } public T Value { get; } public void Dispose() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObservableAsPropertyHelper uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObservableAsPropertyHelper uses methods that may require unreferenced code")] public static ReactiveUI.ObservableAsPropertyHelper Default(T? initialValue = default, System.Reactive.Concurrency.IScheduler? scheduler = null) { } } public static class ObservableFuncMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dynamic observation uses reflection over members that may be trimmed.")] public static System.IObservable ToObservable(this System.Linq.Expressions.Expression> expression, TSource? source, bool beforeChange = false, bool skipInitial = false) { } } public static class ObservableLoggingMixin @@ -769,24 +970,12 @@ namespace ReactiveUI } public static class ObservedChangedMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("TryGetValue uses expression chain analysis and reflection which require dynamic c" + - "ode generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TryGetValue uses expression chain analysis and reflection which may reference mem" + - "bers that could be trimmed.")] public static string GetPropertyName(this ReactiveUI.IObservedChange item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetValue uses expression chain analysis and reflection which require dynamic code" + - " generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetValue uses expression chain analysis and reflection which may reference member" + - "s that could be trimmed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static TValue GetValue(this ReactiveUI.IObservedChange item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetValueOrDefault uses expression chain analysis and reflection which require dyn" + - "amic code generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetValueOrDefault uses expression chain analysis and reflection which may referen" + - "ce members that could be trimmed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static TValue? GetValueOrDefault(this ReactiveUI.IObservedChange item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Value method uses GetValue which requires expression chain analysis and reflectio" + - "n.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Value method uses GetValue which may reference members that could be trimmed.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable Value(this System.IObservable> item) { } } public static class OrderedComparer @@ -801,185 +990,104 @@ namespace ReactiveUI public static System.Collections.Generic.IComparer OrderByDescending(System.Func selector) { } public static System.Collections.Generic.IComparer OrderByDescending(System.Func selector, System.Collections.Generic.IComparer comparer) { } } - public class POCOObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger + public sealed class POCOObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public POCOObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses reflection and type analysis")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses reflection and type analysis")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } - public static class PlatformRegistrationManager - { - public static void SetRegistrationNamespaces(params ReactiveUI.RegistrationNamespace[] namespaces) { } - } public class PlatformRegistrations : ReactiveUI.IWantsToRegisterStuff { public PlatformRegistrations() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Platform registration uses ComponentModelTypeConverter and RxApp which require dy" + - "namic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Platform registration uses ComponentModelTypeConverter and RxApp which may requir" + - "e unreferenced code")] - public void Register(System.Action, System.Type> registerFunction) { } + public void Register(ReactiveUI.IRegistrar registrar) { } } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflectio" + + "n-based invocation.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types and expression graphs which may be trimmed.")] public class PropertyBinderImplementation : ReactiveUI.IPropertyBinderImplementation, Splat.IEnableLogger { public PropertyBinderImplementation() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] public ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, System.Func vmToViewConverter, System.Func viewToVmConverter, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] public ReactiveUI.IReactiveBinding> Bind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, object? conversionHint, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] public System.IDisposable BindTo(System.IObservable observedChange, TTarget? target, System.Linq.Expressions.Expression> propertyExpression, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TTarget : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] public ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func selector) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The handler may use serialization which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The handler may use serialization which may require unreferenced code")] public ReactiveUI.IReactiveBinding OneWayBind(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Uses dynamic binding paths which may require runtime code generation or reflectio" + + "n-based invocation.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class PropertyBindingMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func vmToViewConverter, System.Func viewToVmConverter) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, System.Func vmToViewConverter, System.Func viewToVmConverter, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] [return: System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "view", "isViewModel"})] public static ReactiveUI.IReactiveBinding> Bind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.IObservable? signalViewUpdate, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null, ReactiveUI.IBindingTypeConverter? viewToVMConverterOverride = null, ReactiveUI.TriggerUpdate triggerUpdate = 0) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] public static System.IDisposable BindTo(this System.IObservable @this, TTarget? target, System.Linq.Expressions.Expression> property, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TTarget : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] public static ReactiveUI.IReactiveBinding OneWayBind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, System.Func selector) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("PropertyBindingMixins uses RxApp initialization and expression binding which requ" + - "ire dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PropertyBindingMixins uses RxApp initialization and expression binding which may " + - "require unreferenced code")] public static ReactiveUI.IReactiveBinding OneWayBind(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> viewProperty, object? conversionHint = null, ReactiveUI.IBindingTypeConverter? vmToViewConverterOverride = null) where TViewModel : class where TView : class, ReactiveUI.IViewFor { } } public static class ReactiveCommand { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand Create(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("CreateCombined uses CombinedReactiveCommand which requires dynamic code generatio" + - "n.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CreateCombined uses CombinedReactiveCommand which may require unreferenced code.")] public static ReactiveUI.CombinedReactiveCommand CreateCombined(System.Collections.Generic.IEnumerable> childCommands, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromObservable(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromObservable(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateFromTask(System.Func> execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Action execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] public static ReactiveUI.ReactiveCommand CreateRunInBackground(System.Func execute, System.IObservable? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { } } public abstract class ReactiveCommandBase : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveCommand, ReactiveUI.IReactiveCommand, System.IDisposable, System.IObservable, System.Windows.Input.ICommand @@ -1001,28 +1109,18 @@ namespace ReactiveUI { public static System.IDisposable InvokeCommand(this System.IObservable item, System.Windows.Input.ICommand? command) { } public static System.IDisposable InvokeCommand(this System.IObservable item, ReactiveUI.ReactiveCommandBase? command) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvokeCommand uses WhenAnyValue which requires dynamic code generation for expres" + - "sion tree analysis.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvokeCommand uses WhenAnyValue which may reference members that could be trimmed" + - ".")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable InvokeCommand(this System.IObservable item, TTarget? target, System.Linq.Expressions.Expression> commandProperty) where TTarget : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("InvokeCommand uses WhenAnyValue which requires dynamic code generation for expres" + - "sion tree analysis.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvokeCommand uses WhenAnyValue which may reference members that could be trimmed" + - ".")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable InvokeCommand(this System.IObservable item, TTarget? target, System.Linq.Expressions.Expression?>> commandProperty) where TTarget : class { } } public class ReactiveCommand : ReactiveUI.ReactiveCommandBase { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] protected ReactiveCommand([System.Runtime.CompilerServices.TupleElementNames(new string?[]?[] { "Result", "Cancel"})] System.Func, System.Action>>> execute, System.IObservable? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] protected ReactiveCommand(System.Func> execute, System.IObservable? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { } public override System.IObservable CanExecute { get; } public override System.IObservable IsExecuting { get; } @@ -1032,54 +1130,45 @@ namespace ReactiveUI public override System.IObservable Execute(TParam parameter) { } public override System.IDisposable Subscribe(System.IObserver observer) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static class ReactiveNotifyPropertyChangedMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, bool beforeChange) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName, bool beforeChange) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, bool beforeChange, bool skipInitial) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName, bool beforeChange, bool skipInitial) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, bool beforeChange, bool skipInitial, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Creating Expressions requires unreferenced code because the members being referen" + + "ced by the Expression may be trimmed.")] public static System.IObservable> ObservableForProperty(this TSender? item, string propertyName, bool beforeChange, bool skipInitial, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable ObservableForProperty(this TSender? item, System.Linq.Expressions.Expression> property, System.Func selector, bool beforeChange) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange, bool skipInitial) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange, bool skipInitial, bool suppressWarnings) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IObservable> SubscribeToExpressionChain(this TSender? source, System.Linq.Expressions.Expression? expression, bool beforeChange, bool skipInitial, bool suppressWarnings, bool isDistinct) { } } [System.Runtime.Serialization.DataContract] @@ -1105,8 +1194,6 @@ namespace ReactiveUI public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; public bool AreChangeNotificationsEnabled() { } public System.IDisposable DelayChangeNotifications() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This method uses reflection to access properties by name.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This method uses reflection to access properties by name.")] public System.IDisposable SuppressChangeNotifications() { } } public class ReactivePropertyChangedEventArgs : System.ComponentModel.PropertyChangedEventArgs, ReactiveUI.IReactivePropertyChangedEventArgs @@ -1121,32 +1208,17 @@ namespace ReactiveUI } public static class ReactivePropertyMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses DataAnnotations validation which requires dynamic code generation" + - ".")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses DataAnnotations validation which may require unreferenced code.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("DataAnnotations validation uses reflection to discover attributes and is not trim" + + "-safe. Use manual validation for AOT scenarios.")] public static ReactiveUI.ReactiveProperty AddValidation(this ReactiveUI.ReactiveProperty self, System.Linq.Expressions.Expression?>> selfSelector) { } public static System.IObservable ObserveValidationErrors(this ReactiveUI.ReactiveProperty self) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses ReactiveObject and RxApp which require dynam" + - "ic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses ReactiveObject and RxApp which may require u" + - "nreferenced code")] [System.Runtime.Serialization.DataContract] public class ReactiveProperty : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty, System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable, System.Reactive.Disposables.ICancelable { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] public ReactiveProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] public ReactiveProperty(T? initialValue) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses RxApp which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses RxApp which may require unreferenced code")] public ReactiveProperty(T? initialValue, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveProperty initialization uses ReactiveObject and RxApp which require dynam" + - "ic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveProperty initialization uses ReactiveObject and RxApp which may require u" + - "nreferenced code")] public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { } public bool HasErrors { get; } public bool IsDisposed { get; } @@ -1156,30 +1228,16 @@ namespace ReactiveUI [System.Text.Json.Serialization.JsonInclude] public T Value { get; set; } public event System.EventHandler? ErrorsChanged; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func, System.IObservable> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func, System.IObservable> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func> validator, bool ignoreInitialError = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AddValidationError uses code which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AddValidationError uses code which may require unreferenced code")] public ReactiveUI.ReactiveProperty AddValidationError(System.Func validator, bool ignoreInitialError = false) { } public void CheckValidation() { } public void Dispose() { } protected virtual void Dispose(bool disposing) { } public System.Collections.IEnumerable? GetErrors(string? propertyName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Refresh uses RaisePropertyChanged which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refresh uses RaisePropertyChanged which may require unreferenced code")] public void Refresh() { } public System.IDisposable Subscribe(System.IObserver observer) { } public static ReactiveUI.ReactiveProperty Create() { } @@ -1190,10 +1248,6 @@ namespace ReactiveUI [System.Runtime.Serialization.DataContract] public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveRecord constructor uses extension methods that require dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveRecord constructor uses extension methods that may require unreferenced c" + - "ode")] public ReactiveRecord() { } [System.ComponentModel.Browsable(false)] [System.ComponentModel.DataAnnotations.Display(AutoGenerateField=false, AutoGenerateFilter=false, Order=-1)] @@ -1212,72 +1266,37 @@ namespace ReactiveUI public System.IObservable ThrownExceptions { get; } public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("AreChangeNotificationsEnabled uses extension methods that require dynamic code ge" + - "neration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("AreChangeNotificationsEnabled uses extension methods that may require unreference" + - "d code")] public bool AreChangeNotificationsEnabled() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("DelayChangeNotifications uses extension methods that require dynamic code generat" + - "ion")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("DelayChangeNotifications uses extension methods that may require unreferenced cod" + - "e")] public System.IDisposable DelayChangeNotifications() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SuppressChangeNotifications uses extension methods that require dynamic code gene" + - "ration")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SuppressChangeNotifications uses extension methods that may require unreferenced " + - "code")] public System.IDisposable SuppressChangeNotifications() { } } public static class Reflection { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression tree analysis requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression tree analysis may reference members that could be trimmed")] public static string ExpressionToPropertyNames(System.Linq.Expressions.Expression? expression) { } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Event access may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Reflects over custom delegate Invoke signature; members may be trimmed.")] public static System.Type GetEventArgsTypeForEvent([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type type, string? eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member access may reference members that could be trimmed")] public static System.Func? GetValueFetcherForProperty(System.Reflection.MemberInfo? member) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member access may reference members that could be trimmed")] public static System.Func GetValueFetcherOrThrow(System.Reflection.MemberInfo? member) { } - public static System.Action GetValueSetterForProperty(System.Reflection.MemberInfo? member) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Member access requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Member access may reference members that could be trimmed")] - public static System.Action? GetValueSetterOrThrow(System.Reflection.MemberInfo? member) { } + public static System.Action? GetValueSetterForProperty(System.Reflection.MemberInfo? member) { } + public static System.Action GetValueSetterOrThrow(System.Reflection.MemberInfo? member) { } public static bool IsStatic(this System.Reflection.PropertyInfo item) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Resolves types by name and loads assemblies; types may be trimmed.")] public static System.Type? ReallyFindType(string? type, bool throwOnFailure) { } public static System.Linq.Expressions.Expression Rewrite(System.Linq.Expressions.Expression? expression) { } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Method access may reference members that could be trimmed")] + public static void ThrowIfMethodsNotOverloaded(string callingTypeName, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type targetType, params string[] methodsToCheck) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Inspects declared methods on a runtime type; members may be trimmed.")] public static void ThrowIfMethodsNotOverloaded(string callingTypeName, object targetObject, params string[] methodsToCheck) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TryGetAllValuesForPropertyChain(out ReactiveUI.IObservedChange[] changeValues, object? current, System.Collections.Generic.IEnumerable expressionChain) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TryGetValueForPropertyChain(out TValue changeValue, object? current, System.Collections.Generic.IEnumerable expressionChain) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Expression evaluation requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Expression evaluation may reference members that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static bool TrySetValueToPropertyChain(object? target, System.Collections.Generic.IEnumerable expressionChain, TValue value, bool shouldThrow = true) { } } - public enum RegistrationNamespace - { - None = 0, - Winforms = 1, - Wpf = 2, - Uno = 3, - UnoWinUI = 4, - Blazor = 5, - Drawing = 6, - Avalonia = 7, - Maui = 8, - Uwp = 9, - WinUI = 10, - } public class Registrations : ReactiveUI.IWantsToRegisterStuff { public Registrations() { } - public void Register(System.Action, System.Type> registerFunction) { } + public void Register(ReactiveUI.IRegistrar registrar) { } } public static class RoutableViewModelMixin { @@ -1285,13 +1304,9 @@ namespace ReactiveUI public static System.IObservable WhenNavigatedToObservable(this ReactiveUI.IRoutableViewModel item) { } public static System.IObservable WhenNavigatingFromObservable(this ReactiveUI.IRoutableViewModel item) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutingState uses RxApp and ReactiveCommand which require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutingState uses RxApp and ReactiveCommand which may require unreferenced code")] [System.Runtime.Serialization.DataContract] public class RoutingState : ReactiveUI.ReactiveObject { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutingState uses ReactiveCommand which requires dynamic code generation.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutingState uses ReactiveCommand which may require unreferenced code.")] public RoutingState(System.Reactive.Concurrency.IScheduler? scheduler = null) { } [System.Runtime.Serialization.IgnoreDataMember] [System.Text.Json.Serialization.JsonIgnore] @@ -1318,21 +1333,29 @@ namespace ReactiveUI where T : ReactiveUI.IRoutableViewModel { } public static ReactiveUI.IRoutableViewModel? GetCurrentViewModel(this ReactiveUI.RoutingState item) { } } - public static class RxApp + public static class RxCacheSize { - public const int BigCacheLimit = 256; - public const int SmallCacheLimit = 64; - public static System.IObserver DefaultExceptionHandler { get; set; } - public static System.Reactive.Concurrency.IScheduler MainThreadScheduler { get; set; } - public static bool SuppressViewCommandBindingMessage { get; set; } - public static ReactiveUI.ISuspensionHost SuspensionHost { get; set; } - public static System.Reactive.Concurrency.IScheduler TaskpoolScheduler { get; set; } + public static int BigCacheLimit { get; } + public static int SmallCacheLimit { get; } + } + public static class RxConverters + { + public static ReactiveUI.ConverterService Current { get; } } public static class RxSchedulers { public static System.Reactive.Concurrency.IScheduler MainThreadScheduler { get; set; } + public static bool SuppressViewCommandBindingMessage { get; set; } public static System.Reactive.Concurrency.IScheduler TaskpoolScheduler { get; set; } } + public static class RxState + { + public static System.IObserver DefaultExceptionHandler { get; } + } + public static class RxSuspension + { + public static ReactiveUI.ISuspensionHost SuspensionHost { get; } + } public class ScheduledSubject : System.IDisposable, System.IObservable, System.IObserver, System.Reactive.Subjects.ISubject, System.Reactive.Subjects.ISubject { public ScheduledSubject(System.Reactive.Concurrency.IScheduler scheduler, System.IObserver? defaultObserver = null, System.Reactive.Subjects.ISubject? defaultSubject = null) { } @@ -1343,42 +1366,283 @@ namespace ReactiveUI public void OnNext(T value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } - public class ShortToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class SetMethodBindingConverterRegistry + { + public SetMethodBindingConverterRegistry() { } + public System.Collections.Generic.IEnumerable GetAllConverters() { } + public void Register(ReactiveUI.ISetMethodBindingConverter converter) { } + public ReactiveUI.ISetMethodBindingConverter? TryGetConverter([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? fromType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type? toType) { } + } + public sealed class ShortToNullableShortTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public ShortToNullableShortTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(short from, object? conversionHint, out short? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class ShortToStringTypeConverter : ReactiveUI.BindingTypeConverter { public ShortToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(short from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed class SingleInstanceViewAttribute : System.Attribute { public SingleInstanceViewAttribute() { } } - public class SingleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class SingleToNullableSingleTypeConverter : ReactiveUI.IBindingTypeConverter, ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + { + public SingleToNullableSingleTypeConverter() { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvert(float from, object? conversionHint, out float? result) { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class SingleToStringTypeConverter : ReactiveUI.BindingTypeConverter { public SingleToStringTypeConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object result) { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(float from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } - public class StringConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger + public sealed class StringConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger { public StringConverter() { } - public int GetAffinityForObjects(System.Type fromType, System.Type toType) { } - public bool TryConvert(object? from, System.Type toType, object? conversionHint, out object? result) { } + public System.Type FromType { get; } + public System.Type ToType { get; } + public int GetAffinityForObjects() { } + public bool TryConvertTyped(object? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) { } + } + public sealed class StringToBooleanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToBooleanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out bool result) { } + } + public sealed class StringToByteTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToByteTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out byte result) { } + } + public sealed class StringToDateOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDateOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.DateOnly result) { } + } + public sealed class StringToDateTimeOffsetTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDateTimeOffsetTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.DateTimeOffset result) { } + } + public sealed class StringToDateTimeTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDateTimeTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.DateTime result) { } + } + public sealed class StringToDecimalTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDecimalTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out decimal result) { } + } + public sealed class StringToDoubleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToDoubleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out double result) { } + } + public sealed class StringToGuidTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToGuidTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Guid result) { } + } + public sealed class StringToIntegerTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToIntegerTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out int result) { } + } + public sealed class StringToLongTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToLongTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out long result) { } + } + public sealed class StringToNullableBooleanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableBooleanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out bool? result) { } + } + public sealed class StringToNullableByteTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableByteTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out byte? result) { } + } + public sealed class StringToNullableDateOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDateOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.DateOnly? result) { } + } + public sealed class StringToNullableDateTimeOffsetTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDateTimeOffsetTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.DateTimeOffset? result) { } + } + public sealed class StringToNullableDateTimeTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDateTimeTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.DateTime? result) { } + } + public sealed class StringToNullableDecimalTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDecimalTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out decimal? result) { } + } + public sealed class StringToNullableDoubleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableDoubleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out double? result) { } + } + public sealed class StringToNullableGuidTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableGuidTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.Guid? result) { } + } + public sealed class StringToNullableIntegerTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableIntegerTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out int? result) { } + } + public sealed class StringToNullableLongTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableLongTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out long? result) { } + } + public sealed class StringToNullableShortTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableShortTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out short? result) { } + } + public sealed class StringToNullableSingleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableSingleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out float? result) { } + } + public sealed class StringToNullableTimeOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableTimeOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.TimeOnly? result) { } + } + public sealed class StringToNullableTimeSpanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToNullableTimeSpanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(true)] out System.TimeSpan? result) { } + } + public sealed class StringToShortTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToShortTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out short result) { } + } + public sealed class StringToSingleTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToSingleTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out float result) { } + } + public sealed class StringToTimeOnlyTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToTimeOnlyTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.TimeOnly result) { } + } + public sealed class StringToTimeSpanTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToTimeSpanTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.TimeSpan result) { } + } + public sealed class StringToUriTypeConverter : ReactiveUI.BindingTypeConverter + { + public StringToUriTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(string? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Uri? result) { } } public static class SuspensionHostExtensions { + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode(@"This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. Prefer GetAppState(ISuspensionHost) used with SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(@"This overload may invoke ISuspensionDriver.LoadState(), which is commonly reflection-based. Prefer GetAppState(ISuspensionHost) used with SetupDefaultSuspendResume(..., JsonTypeInfo, ...) for trimming/AOT scenarios.")] public static T GetAppState(this ReactiveUI.ISuspensionHost item) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ObserveAppState uses WhenAny which requires dynamic code generation for expressio" + - "n tree analysis")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ObserveAppState uses WhenAny which may reference members that could be trimmed")] + public static TAppState GetAppState(this ReactiveUI.Interfaces.ISuspensionHost item) + where TAppState : class { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This overload uses WhenAny, which can require unreferenced/dynamic code in trimmi" + + "ng/AOT scenarios. Prefer ObserveAppState(ISuspensionHost) " + + "for trimming/AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This overload uses WhenAny, which can require unreferenced/dynamic code in trimmi" + + "ng/AOT scenarios. Prefer ObserveAppState(ISuspensionHost) " + + "for trimming/AOT scenarios.")] public static System.IObservable ObserveAppState(this ReactiveUI.ISuspensionHost item) where T : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("SetupDefaultSuspendResume uses ISuspensionDriver which may require dynamic code g" + - "eneration for serialization")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("SetupDefaultSuspendResume uses ISuspensionDriver which may require unreferenced c" + - "ode for serialization")] + public static System.IObservable ObserveAppState(this ReactiveUI.Interfaces.ISuspensionHost item) + where TAppState : class { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("This overload may invoke ISuspensionDriver.LoadState()/SaveState(T), which are" + + " commonly reflection-based. Prefer SetupDefaultSuspendResume(..., Jso" + + "nTypeInfo, ...) for trimming/AOT scenarios.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This overload may invoke ISuspensionDriver.LoadState()/SaveState(T), which are" + + " commonly reflection-based. Prefer SetupDefaultSuspendResume(..., Jso" + + "nTypeInfo, ...) for trimming/AOT scenarios.")] public static System.IDisposable SetupDefaultSuspendResume(this ReactiveUI.ISuspensionHost item, ReactiveUI.ISuspensionDriver? driver = null) { } + public static System.IDisposable SetupDefaultSuspendResume(this ReactiveUI.Interfaces.ISuspensionHost item, System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo, ReactiveUI.ISuspensionDriver? driver = null) + where TAppState : class { } + } + public class SuspensionHost : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveObject, ReactiveUI.ISuspensionHost, ReactiveUI.Interfaces.ISuspensionHost, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IDisposable + { + public SuspensionHost() { } + public TAppState AppStateValue { get; set; } + public System.IObservable AppStateValueChanged { get; } + public System.Func? CreateNewAppStateTyped { get; set; } + public System.IObservable IsContinuing { get; set; } + public System.IObservable IsLaunchingNew { get; set; } + public System.IObservable IsResuming { get; set; } + public System.IObservable IsUnpausing { get; set; } + public System.IObservable ShouldInvalidateState { get; set; } + public System.IObservable ShouldPersistState { get; set; } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + } + public sealed class TimeOnlyToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public TimeOnlyToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeOnly from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } + public sealed class TimeSpanToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public TimeSpanToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.TimeSpan from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } } public enum TriggerUpdate { @@ -1400,6 +1664,12 @@ namespace ReactiveUI public TInput Input { get; } public ReactiveUI.Interaction? Interaction { get; } } + public sealed class UriToStringTypeConverter : ReactiveUI.BindingTypeConverter + { + public UriToStringTypeConverter() { } + public override int GetAffinityForObjects() { } + public override bool TryConvert(System.Uri? from, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) { } + } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed class ViewContractAttribute : System.Attribute { @@ -1408,23 +1678,18 @@ namespace ReactiveUI } public static class ViewForMixins { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Action> block) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Func> block) { } public static void WhenActivated(this ReactiveUI.IActivatableViewModel item, System.Action> block) { } public static void WhenActivated(this ReactiveUI.IActivatableViewModel item, System.Action block) { } public static void WhenActivated(this ReactiveUI.IActivatableViewModel item, System.Func> block) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Action> block, ReactiveUI.IViewFor view) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Action block, ReactiveUI.IViewFor? view = null) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenActivated uses reflection which requires dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenActivated may reference types that could be trimmed")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static System.IDisposable WhenActivated(this ReactiveUI.IActivatableView item, System.Func> block, ReactiveUI.IViewFor? view) { } } public static class ViewLocator @@ -1437,6 +1702,18 @@ namespace ReactiveUI public ViewLocatorNotFoundException(string message) { } public ViewLocatorNotFoundException(string message, System.Exception innerException) { } } + public sealed class ViewMappingBuilder + { + public ReactiveUI.ViewMappingBuilder Map(string? contract = null) + where TViewModel : class + where TView : class, ReactiveUI.IViewFor, new () { } + public ReactiveUI.ViewMappingBuilder Map(System.Func factory, string? contract = null) + where TViewModel : class + where TView : class, ReactiveUI.IViewFor { } + public ReactiveUI.ViewMappingBuilder MapFromServiceLocator(string? contract = null) + where TViewModel : class + where TView : class, ReactiveUI.IViewFor { } + } public sealed class ViewModelActivator : System.IDisposable { public ViewModelActivator() { } @@ -1454,716 +1731,205 @@ namespace ReactiveUI public System.IDisposable Schedule(TState state, System.DateTimeOffset dueTime, System.Func action) { } public System.IDisposable Schedule(TState state, System.TimeSpan dueTime, System.Func action) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class WhenAnyMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, System.Func, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, System.Func, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, System.Func, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, System.Func, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAny uses expression trees which require dynamic code generation in AOT scenar" + - "ios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAny may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAny(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Func, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Func, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Func, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Func, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Linq.Expressions.Expression? property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyDynamic uses expression trees which require dynamic code generation in AOT" + - " scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyDynamic may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyDynamic(this TSender? sender, System.Linq.Expressions.Expression? property1, System.Linq.Expressions.Expression? property2, System.Linq.Expressions.Expression? property3, System.Linq.Expressions.Expression? property4, System.Linq.Expressions.Expression? property5, System.Linq.Expressions.Expression? property6, System.Linq.Expressions.Expression? property7, System.Linq.Expressions.Expression? property8, System.Linq.Expressions.Expression? property9, System.Linq.Expressions.Expression? property10, System.Linq.Expressions.Expression? property11, System.Linq.Expressions.Expression? property12, System.Func, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, ReactiveUI.IObservedChange, TRet> selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string propertyName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string propertyName, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable> WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func selector) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, System.Linq.Expressions.Expression> property1, System.Linq.Expressions.Expression> property2, System.Linq.Expressions.Expression> property3, System.Linq.Expressions.Expression> property4, System.Linq.Expressions.Expression> property5, System.Linq.Expressions.Expression> property6, System.Linq.Expressions.Expression> property7, System.Linq.Expressions.Expression> property8, System.Linq.Expressions.Expression> property9, System.Linq.Expressions.Expression> property10, System.Linq.Expressions.Expression> property11, System.Linq.Expressions.Expression> property12, System.Func selector, bool isDistinct) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyValue uses expression trees which require dynamic code generation in AOT s" + - "cenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyValue may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyValue(this TSender? sender, string property1Name, string property2Name, string property3Name, string property4Name, string property5Name, string property6Name, string property7Name, string property8Name, string property9Name, string property10Name, string property11Name, string property12Name, System.Func selector, bool isDistinct) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Evaluates expression-based member chains via reflection; members may be trimmed.")] public static class WhenAnyObservableMixin { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11, System.Linq.Expressions.Expression?>> obs12) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11, System.Func selector) where TSender : class { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WhenAnyObservable uses expression trees which require dynamic code generation in " + - "AOT scenarios.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WhenAnyObservable may reference members that could be trimmed in AOT scenarios.")] public static System.IObservable WhenAnyObservable(this TSender? sender, System.Linq.Expressions.Expression?>> obs1, System.Linq.Expressions.Expression?>> obs2, System.Linq.Expressions.Expression?>> obs3, System.Linq.Expressions.Expression?>> obs4, System.Linq.Expressions.Expression?>> obs5, System.Linq.Expressions.Expression?>> obs6, System.Linq.Expressions.Expression?>> obs7, System.Linq.Expressions.Expression?>> obs8, System.Linq.Expressions.Expression?>> obs9, System.Linq.Expressions.Expression?>> obs10, System.Linq.Expressions.Expression?>> obs11, System.Linq.Expressions.Expression?>> obs12, System.Func selector) where TSender : class { } } @@ -2172,6 +1938,7 @@ namespace ReactiveUI.Builder { public static class BuilderMixins { + public static ReactiveUI.Builder.IReactiveUIBuilder BuildApp(this Splat.Builder.IAppBuilder appBuilder) { } public static ReactiveUI.Builder.IReactiveUIBuilder ConfigureMessageBus(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action configure) { } public static ReactiveUI.Builder.IReactiveUIBuilder ConfigureSuspensionDriver(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action configure) { } public static ReactiveUI.Builder.IReactiveUIBuilder ConfigureViewLocator(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action configure) { } @@ -2187,9 +1954,18 @@ namespace ReactiveUI.Builder where TViewModel : class, ReactiveUI.IReactiveObject { } public static ReactiveUI.Builder.IReactiveUIBuilder RegisterViewModel(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder) where TViewModel : class, ReactiveUI.IReactiveObject, new () { } + public static ReactiveUI.Builder.IReactiveUIBuilder RegisterViews(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Action configure) { } public static ReactiveUI.Builder.IReactiveUIBuilder UsingSplatBuilder(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, System.Action? appBuilder) { } public static ReactiveUI.Builder.IReactiveUIBuilder UsingSplatModule(this ReactiveUI.Builder.IReactiveUIBuilder builder, T registrationModule) where T : Splat.Builder.IModule { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.IBindingTypeConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func factory) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.BindingTypeConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func> factory) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConverters(this ReactiveUI.Builder.IReactiveUIBuilder builder, params ReactiveUI.IBindingTypeConverter[] converters) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithConvertersFrom(this ReactiveUI.Builder.IReactiveUIBuilder builder, Splat.IReadonlyDependencyResolver resolver) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.IBindingFallbackConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func factory) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } @@ -2207,15 +1983,18 @@ namespace ReactiveUI.Builder public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIInstance WithInstance(this ReactiveUI.Builder.IReactiveUIInstance reactiveUIInstance, System.Action action) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithMainThreadScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + public static ReactiveUI.Builder.IReactiveUIBuilder WithMessageBus(this ReactiveUI.Builder.IReactiveUIBuilder reactiveUIBuilder, ReactiveUI.IMessageBus messageBus) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithPlatformModule(this ReactiveUI.Builder.IReactiveUIBuilder builder) where T : ReactiveUI.IWantsToRegisterStuff, new () { } public static ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Action configureAction) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithRegistrationOnBuild(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Action configureAction) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, ReactiveUI.ISetMethodBindingConverter converter) { } + public static ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Func factory) { } public static ReactiveUI.Builder.IReactiveUIBuilder WithTaskPoolScheduler(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + public static ReactiveUI.Builder.IReactiveUIBuilder WithViewModule(this ReactiveUI.Builder.IReactiveUIBuilder builder) + where TModule : ReactiveUI.IViewModule, new () { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] public static ReactiveUI.Builder.IReactiveUIBuilder WithViewsFromAssembly(this ReactiveUI.Builder.IReactiveUIBuilder builder, System.Reflection.Assembly assembly) { } } public interface IReactiveUIBuilder : Splat.Builder.IAppBuilder @@ -2238,6 +2017,8 @@ namespace ReactiveUI.Builder where TViewModel : class, ReactiveUI.IReactiveObject, new (); ReactiveUI.Builder.IReactiveUIBuilder UsingSplatModule(T registrationModule) where T : Splat.Builder.IModule; + ReactiveUI.Builder.IReactiveUIBuilder WithCacheSizes(int smallCacheLimit, int bigCacheLimit); + ReactiveUI.Builder.IReactiveUIBuilder WithExceptionHandler(System.IObserver exceptionHandler); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); @@ -2255,19 +2036,17 @@ namespace ReactiveUI.Builder ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action); ReactiveUI.Builder.IReactiveUIBuilder WithMainThreadScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + ReactiveUI.Builder.IReactiveUIBuilder WithMessageBus(ReactiveUI.IMessageBus messageBus); ReactiveUI.Builder.IReactiveUIBuilder WithPlatformModule() where T : ReactiveUI.IWantsToRegisterStuff, new (); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ProcessRegistrationForNamespace uses reflection to locate types which may be trim" + - "med.")] ReactiveUI.Builder.IReactiveUIBuilder WithPlatformServices(); ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(System.Action configureAction); ReactiveUI.Builder.IReactiveUIBuilder WithRegistrationOnBuild(System.Action configureAction); + ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost(); + ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost(); ReactiveUI.Builder.IReactiveUIBuilder WithTaskPoolScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true); - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] ReactiveUI.Builder.IReactiveUIBuilder WithViewsFromAssembly(System.Reflection.Assembly assembly); } public interface IReactiveUIInstance : Splat.Builder.IAppInstance @@ -2278,6 +2057,7 @@ namespace ReactiveUI.Builder public sealed class ReactiveUIBuilder : Splat.Builder.AppBuilder, ReactiveUI.Builder.IReactiveUIBuilder, ReactiveUI.Builder.IReactiveUIInstance, Splat.Builder.IAppBuilder, Splat.Builder.IAppInstance { public ReactiveUIBuilder(Splat.IMutableDependencyResolver resolver, Splat.IReadonlyDependencyResolver? current) { } + public ReactiveUI.ConverterService ConverterService { get; } public System.Reactive.Concurrency.IScheduler? MainThreadScheduler { get; } public System.Reactive.Concurrency.IScheduler? TaskpoolScheduler { get; } public ReactiveUI.Builder.IReactiveUIInstance BuildApp() { } @@ -2299,9 +2079,16 @@ namespace ReactiveUI.Builder public ReactiveUI.Builder.IReactiveUIBuilder UsingSplatBuilder(System.Action appBuilder) { } public ReactiveUI.Builder.IReactiveUIBuilder UsingSplatModule(T registrationModule) where T : Splat.Builder.IModule { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] + public ReactiveUI.Builder.IReactiveUIBuilder WithCacheSizes(int smallCacheLimit, int bigCacheLimit) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(ReactiveUI.IBindingTypeConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(System.Func factory) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(ReactiveUI.BindingTypeConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConverter(System.Func> factory) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithConvertersFrom(Splat.IReadonlyDependencyResolver resolver) { } public override Splat.Builder.IAppBuilder WithCoreServices() { } + public ReactiveUI.Builder.IReactiveUIBuilder WithExceptionHandler(System.IObserver exceptionHandler) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(ReactiveUI.IBindingFallbackConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithFallbackConverter(System.Func factory) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } @@ -2319,23 +2106,35 @@ namespace ReactiveUI.Builder public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIInstance WithInstance(System.Action action) { } public ReactiveUI.Builder.IReactiveUIBuilder WithMainThreadScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + public ReactiveUI.Builder.IReactiveUIBuilder WithMessageBus(ReactiveUI.IMessageBus messageBus) { } public ReactiveUI.Builder.IReactiveUIBuilder WithPlatformModule() where T : ReactiveUI.IWantsToRegisterStuff, new () { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ReactiveUI.IWantsToRegisterStuff.Register(Action, Type>)")] public ReactiveUI.Builder.IReactiveUIBuilder WithPlatformServices() { } + public ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(ReactiveUI.IWantsToRegisterStuff registration) { } public ReactiveUI.Builder.IReactiveUIBuilder WithRegistration(System.Action configureAction) { } public ReactiveUI.Builder.IReactiveUIBuilder WithRegistrationOnBuild(System.Action configureAction) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(ReactiveUI.ISetMethodBindingConverter converter) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSetMethodConverter(System.Func factory) { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost() { } + public ReactiveUI.Builder.IReactiveUIBuilder WithSuspensionHost() { } public ReactiveUI.Builder.IReactiveUIBuilder WithTaskPoolScheduler(System.Reactive.Concurrency.IScheduler scheduler, bool setRxApp = true) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scans assembly for IViewFor implementations using reflection. For AOT compatibili" + + "ty, use the ReactiveUIBuilder pattern to RegisterView explicitly.")] public ReactiveUI.Builder.IReactiveUIBuilder WithViewsFromAssembly(System.Reflection.Assembly assembly) { } } public static class RxAppBuilder { public static ReactiveUI.Builder.ReactiveUIBuilder CreateReactiveUIBuilder() { } public static ReactiveUI.Builder.ReactiveUIBuilder CreateReactiveUIBuilder(this Splat.IMutableDependencyResolver resolver) { } + public static void EnsureInitialized() { } + } +} +namespace ReactiveUI.Interfaces +{ + public interface ISuspensionHost : ReactiveUI.IReactiveObject, ReactiveUI.ISuspensionHost, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging + { + TAppState AppStateValue { get; set; } + System.IObservable AppStateValueChanged { get; } + System.Func? CreateNewAppStateTyped { get; set; } } } \ No newline at end of file diff --git a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet10_0.verified.txt b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet10_0.verified.txt index 98d8249036..423a4aa4e2 100644 --- a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet10_0.verified.txt +++ b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet10_0.verified.txt @@ -41,24 +41,14 @@ public static Microsoft.Reactive.Testing.Recorded> OnCompletedAt(this Microsoft.Reactive.Testing.TestScheduler scheduler, double milliseconds) { } public static Microsoft.Reactive.Testing.Recorded> OnErrorAt(this Microsoft.Reactive.Testing.TestScheduler scheduler, double milliseconds, System.Exception ex) { } public static Microsoft.Reactive.Testing.Recorded> OnNextAt(this Microsoft.Reactive.Testing.TestScheduler scheduler, double milliseconds, T value) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("With uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("With uses methods that may require unreferenced code")] public static void With(this T scheduler, System.Action block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("With uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("With uses methods that may require unreferenced code")] public static TRet With(this T scheduler, System.Func block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WithAsync uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WithAsync uses methods that may require unreferenced code")] public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WithAsync uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WithAsync uses methods that may require unreferenced code")] public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func> block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WithScheduler uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WithScheduler uses methods that may require unreferenced code")] public static System.IDisposable WithScheduler(System.Reactive.Concurrency.IScheduler scheduler) { } } public class TestSequencer : System.IDisposable @@ -70,4 +60,4 @@ public void Dispose() { } protected virtual void Dispose(bool disposing) { } } -} +} \ No newline at end of file diff --git a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet8_0.verified.txt b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet8_0.verified.txt index 98d8249036..423a4aa4e2 100644 --- a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet8_0.verified.txt +++ b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet8_0.verified.txt @@ -41,24 +41,14 @@ public static Microsoft.Reactive.Testing.Recorded> OnCompletedAt(this Microsoft.Reactive.Testing.TestScheduler scheduler, double milliseconds) { } public static Microsoft.Reactive.Testing.Recorded> OnErrorAt(this Microsoft.Reactive.Testing.TestScheduler scheduler, double milliseconds, System.Exception ex) { } public static Microsoft.Reactive.Testing.Recorded> OnNextAt(this Microsoft.Reactive.Testing.TestScheduler scheduler, double milliseconds, T value) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("With uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("With uses methods that may require unreferenced code")] public static void With(this T scheduler, System.Action block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("With uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("With uses methods that may require unreferenced code")] public static TRet With(this T scheduler, System.Func block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WithAsync uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WithAsync uses methods that may require unreferenced code")] public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WithAsync uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WithAsync uses methods that may require unreferenced code")] public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func> block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WithScheduler uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WithScheduler uses methods that may require unreferenced code")] public static System.IDisposable WithScheduler(System.Reactive.Concurrency.IScheduler scheduler) { } } public class TestSequencer : System.IDisposable @@ -70,4 +60,4 @@ public void Dispose() { } protected virtual void Dispose(bool disposing) { } } -} +} \ No newline at end of file diff --git a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet9_0.verified.txt b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet9_0.verified.txt index 98d8249036..423a4aa4e2 100644 --- a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet9_0.verified.txt +++ b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.Testing.DotNet9_0.verified.txt @@ -41,24 +41,14 @@ public static Microsoft.Reactive.Testing.Recorded> OnCompletedAt(this Microsoft.Reactive.Testing.TestScheduler scheduler, double milliseconds) { } public static Microsoft.Reactive.Testing.Recorded> OnErrorAt(this Microsoft.Reactive.Testing.TestScheduler scheduler, double milliseconds, System.Exception ex) { } public static Microsoft.Reactive.Testing.Recorded> OnNextAt(this Microsoft.Reactive.Testing.TestScheduler scheduler, double milliseconds, T value) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("With uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("With uses methods that may require unreferenced code")] public static void With(this T scheduler, System.Action block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("With uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("With uses methods that may require unreferenced code")] public static TRet With(this T scheduler, System.Func block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WithAsync uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WithAsync uses methods that may require unreferenced code")] public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WithAsync uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WithAsync uses methods that may require unreferenced code")] public static System.Threading.Tasks.Task WithAsync(this T scheduler, System.Func> block) where T : System.Reactive.Concurrency.IScheduler { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WithScheduler uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WithScheduler uses methods that may require unreferenced code")] public static System.IDisposable WithScheduler(System.Reactive.Concurrency.IScheduler scheduler) { } } public class TestSequencer : System.IDisposable @@ -70,4 +60,4 @@ public void Dispose() { } protected virtual void Dispose(bool disposing) { } } -} +} \ No newline at end of file diff --git a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.cs b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.cs index 27be9dd787..59f4ab73e4 100644 --- a/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.cs +++ b/src/tests/ReactiveUI.Tests/API/ApiApprovalTests.cs @@ -3,33 +3,22 @@ // 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.Assertions; -using TUnit.Assertions.Extensions; -using TUnit.Core; +using ReactiveUI.Tests.Utilities; using TUnit.Core.Enums; -using static TUnit.Assertions.Assert; - namespace ReactiveUI.Tests.API; /// -/// Checks to make sure that the API is consistent with previous releases, and new API changes are highlighted. +/// Checks to make sure that the API is consistent with previous releases, and new API changes are highlighted. /// [ExcludeFromCodeCoverage] [RunOn(OS.Windows)] public class ApiApprovalTests { /// - /// Generates public API for the ReactiveUI.Testing API. - /// - /// A task to monitor the process. - [Test] - public Task Testing() => typeof(Testing.SchedulerExtensions).Assembly.CheckApproval(["ReactiveUI"]); - - /// - /// Generates public API for the ReactiveUI API. + /// Generates public API for the ReactiveUI API. /// /// A task to monitor the process. [Test] - public Task ReactiveUI() => typeof(RxApp).Assembly.CheckApproval(["ReactiveUI"]); + public Task ReactiveUI() => typeof(RxState).Assembly.CheckApproval(["ReactiveUI"]); } diff --git a/src/tests/ReactiveUI.Tests/Activation/ActivatingView.cs b/src/tests/ReactiveUI.Tests/Activation/ActivatingView.cs index fd59ba885f..00f3cac179 100644 --- a/src/tests/ReactiveUI.Tests/Activation/ActivatingView.cs +++ b/src/tests/ReactiveUI.Tests/Activation/ActivatingView.cs @@ -3,17 +3,17 @@ // 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; +namespace ReactiveUI.Tests.Activation; /// -/// A view which simulates a activation. +/// A view which simulates a activation. /// public sealed class ActivatingView : ReactiveObject, IViewFor, ICanActivate, IDisposable { private ActivatingViewModel? _viewModel; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ActivatingView() => this.WhenActivated(d => @@ -23,27 +23,32 @@ public ActivatingView() => }); /// - /// Gets the loaded. + /// Gets an observable that signals when the view is activated. /// - public Subject Loaded { get; } = new(); + public IObservable Activated => Loaded; /// - /// Gets the unloaded. + /// Gets an observable that signals when the view is deactivated. /// - public Subject Unloaded { get; } = new(); + public IObservable Deactivated => Unloaded; /// - /// Gets an observable that signals when the view is activated. + /// Gets the loaded. /// - public IObservable Activated => Loaded; + public Subject Loaded { get; } = new(); /// - /// Gets an observable that signals when the view is deactivated. + /// Gets the unloaded. /// - public IObservable Deactivated => Unloaded; + public Subject Unloaded { get; } = new(); /// - /// Gets or sets the view model. + /// Gets or sets the active count. + /// + public int IsActiveCount { get; set; } + + /// + /// Gets or sets the view model. /// public ActivatingViewModel? ViewModel { @@ -52,7 +57,7 @@ public ActivatingViewModel? ViewModel } /// - /// Gets or sets the view model. + /// Gets or sets the view model. /// object? IViewFor.ViewModel { @@ -61,12 +66,7 @@ public ActivatingViewModel? ViewModel } /// - /// Gets or sets the active count. - /// - public int IsActiveCount { get; set; } - - /// - /// Releases unmanaged and - optionally - managed resources. + /// Releases unmanaged and - optionally - managed resources. /// public void Dispose() { diff --git a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewFetcher.cs b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewFetcher.cs index 566aff888c..e87d209186 100644 --- a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewFetcher.cs +++ b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewFetcher.cs @@ -3,34 +3,20 @@ // 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; +namespace ReactiveUI.Tests.Activation; /// -/// Simulates a activating view fetcher. +/// Simulates a activating view fetcher. /// public class ActivatingViewFetcher : IActivationForViewFetcher { /// - /// Determines the priority that the Activation View Fetcher - /// will be able to process the view type. - /// 0 means it cannot activate the View, value larger than 0 - /// indicates it can activate the View. - /// The class derived off IActivationForViewFetcher which returns - /// the highest affinity value will be used to activate the View. - /// - /// The type for the View. - /// - /// The affinity value which is equal to 0 or above. - /// - public int GetAffinityForView(Type view) => view == typeof(ActivatingView) ? 100 : 0; - - /// - /// Gets a Observable which will activate the View. - /// This is called after the GetAffinityForView method. + /// Gets a Observable which will activate the View. + /// This is called after the GetAffinityForView method. /// /// The view to get the activation observable for. /// - /// A Observable which will returns if Activation was successful. + /// A Observable which will returns if Activation was successful. /// /// The view is null. public IObservable GetActivationForView(IActivatableView view) @@ -42,4 +28,18 @@ public IObservable GetActivationForView(IActivatableView view) return av.Loaded.Select(static _ => true).Merge(av.Unloaded.Select(static _ => false)); } + + /// + /// Determines the priority that the Activation View Fetcher + /// will be able to process the view type. + /// 0 means it cannot activate the View, value larger than 0 + /// indicates it can activate the View. + /// The class derived off IActivationForViewFetcher which returns + /// the highest affinity value will be used to activate the View. + /// + /// The type for the View. + /// + /// The affinity value which is equal to 0 or above. + /// + public int GetAffinityForView(Type view) => view == typeof(ActivatingView) ? 100 : 0; } diff --git a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModel.cs b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModel.cs index 832ea63332..9da92b65a8 100644 --- a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModel.cs @@ -3,15 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Activation; /// -/// Simulates a activating view model. +/// Simulates a activating view model. /// public class ActivatingViewModel : ReactiveObject, IActivatableViewModel { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ActivatingViewModel() { @@ -25,12 +25,12 @@ public ActivatingViewModel() } /// - /// Gets or sets the Activator which will be used by the View when Activation/Deactivation occurs. + /// Gets or sets the Activator which will be used by the View when Activation/Deactivation occurs. /// public ViewModelActivator Activator { get; protected set; } /// - /// Gets or sets the active count. + /// Gets or sets the active count. /// public int IsActiveCount { get; protected set; } } diff --git a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModelTests.cs b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModelTests.cs index 16f5b480cf..d4df941cad 100644 --- a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModelTests.cs +++ b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModelTests.cs @@ -3,14 +3,14 @@ // 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; +namespace ReactiveUI.Tests.Activation; public class ActivatingViewModelTests { /// - /// Tests for the activation to make sure it activates the appropriate number of times. + /// Tests for the activation to make sure it activates the appropriate number of times. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ActivationsGetRefCounted() { @@ -32,9 +32,9 @@ public async Task ActivationsGetRefCounted() } /// - /// Tests to make sure the activations of derived classes don't get stomped. + /// Tests to make sure the activations of derived classes don't get stomped. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task DerivedActivationsDontGetStomped() { diff --git a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewTests.cs b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewTests.cs index 69a2f9d44f..97271ba5b9 100644 --- a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewTests.cs +++ b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewTests.cs @@ -3,32 +3,33 @@ // 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; using Splat.Builder; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Activation; public class ActivatingViewTests { /// - /// Tests to make sure that views generally activate. + /// Tests to make sure that views generally activate. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ActivatingViewSmokeTest() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.InitializeSplat(); - locator.InitializeReactiveUI(); - locator.Register(static () => new ActivatingViewFetcher(), typeof(IActivationForViewFetcher)); + locator + .CreateReactiveUIBuilder() + .WithCoreServices() + .WithCustomRegistration(builder => + builder.Register(() => new ActivatingViewFetcher())) + .BuildApp(); using (locator.WithResolver()) { var vm = new ActivatingViewModel(); - var fixture = new ActivatingView - { - ViewModel = vm - }; + var fixture = new ActivatingView { ViewModel = vm }; using (Assert.Multiple()) { await Assert.That(vm.IsActiveCount).IsEqualTo(0); @@ -52,25 +53,25 @@ public async Task ActivatingViewSmokeTest() } /// - /// Tests for making sure nulling the view model deactivate it. + /// Tests the can unload and load view again. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task NullingViewModelDeactivateIt() + public async Task CanUnloadAndLoadViewAgain() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.InitializeSplat(); - locator.InitializeReactiveUI(); - locator.Register(static () => new ActivatingViewFetcher(), typeof(IActivationForViewFetcher)); + locator + .CreateReactiveUIBuilder() + .WithCoreServices() + .WithCustomRegistration(builder => + builder.Register(() => new ActivatingViewFetcher())) + .BuildApp(); using (locator.WithResolver()) { var vm = new ActivatingViewModel(); - var fixture = new ActivatingView - { - ViewModel = vm - }; + var fixture = new ActivatingView { ViewModel = vm }; using (Assert.Multiple()) { await Assert.That(vm.IsActiveCount).IsEqualTo(0); @@ -84,31 +85,42 @@ public async Task NullingViewModelDeactivateIt() await Assert.That(fixture.IsActiveCount).IsEqualTo(1); } - fixture.ViewModel = null; - await Assert.That(vm.IsActiveCount).IsEqualTo(0); + fixture.Unloaded.OnNext(Unit.Default); + using (Assert.Multiple()) + { + await Assert.That(vm.IsActiveCount).IsEqualTo(0); + await Assert.That(fixture.IsActiveCount).IsEqualTo(0); + } + + fixture.Loaded.OnNext(Unit.Default); + using (Assert.Multiple()) + { + await Assert.That(vm.IsActiveCount).IsEqualTo(1); + await Assert.That(fixture.IsActiveCount).IsEqualTo(1); + } } } /// - /// Tests switching the view model deactivates it. + /// Tests for making sure nulling the view model deactivate it. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task SwitchingViewModelDeactivatesIt() + public async Task NullingViewModelDeactivateIt() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.InitializeSplat(); - locator.InitializeReactiveUI(); - locator.Register(static () => new ActivatingViewFetcher(), typeof(IActivationForViewFetcher)); + locator + .CreateReactiveUIBuilder() + .WithCoreServices() + .WithCustomRegistration(builder => + builder.Register(() => new ActivatingViewFetcher())) + .BuildApp(); using (locator.WithResolver()) { var vm = new ActivatingViewModel(); - var fixture = new ActivatingView - { - ViewModel = vm - }; + var fixture = new ActivatingView { ViewModel = vm }; using (Assert.Multiple()) { await Assert.That(vm.IsActiveCount).IsEqualTo(0); @@ -122,30 +134,26 @@ public async Task SwitchingViewModelDeactivatesIt() await Assert.That(fixture.IsActiveCount).IsEqualTo(1); } - var newVm = new ActivatingViewModel(); - await Assert.That(newVm.IsActiveCount).IsEqualTo(0); - - fixture.ViewModel = newVm; - using (Assert.Multiple()) - { - await Assert.That(vm.IsActiveCount).IsEqualTo(0); - await Assert.That(newVm.IsActiveCount).IsEqualTo(1); - } + fixture.ViewModel = null; + await Assert.That(vm.IsActiveCount).IsEqualTo(0); } } /// - /// Tests setting the view model after loaded loads it. + /// Tests setting the view model after loaded loads it. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task SettingViewModelAfterLoadedLoadsIt() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.InitializeSplat(); - locator.InitializeReactiveUI(); - locator.Register(static () => new ActivatingViewFetcher(), typeof(IActivationForViewFetcher)); + locator + .CreateReactiveUIBuilder() + .WithCoreServices() + .WithCustomRegistration(builder => + builder.Register(() => new ActivatingViewFetcher())) + .BuildApp(); using (locator.WithResolver()) { @@ -178,25 +186,25 @@ public async Task SettingViewModelAfterLoadedLoadsIt() } /// - /// Tests the can unload and load view again. + /// Tests switching the view model deactivates it. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task CanUnloadAndLoadViewAgain() + public async Task SwitchingViewModelDeactivatesIt() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.InitializeSplat(); - locator.InitializeReactiveUI(); - locator.Register(static () => new ActivatingViewFetcher(), typeof(IActivationForViewFetcher)); + locator + .CreateReactiveUIBuilder() + .WithCoreServices() + .WithCustomRegistration(builder => + builder.Register(() => new ActivatingViewFetcher())) + .BuildApp(); using (locator.WithResolver()) { var vm = new ActivatingViewModel(); - var fixture = new ActivatingView - { - ViewModel = vm - }; + var fixture = new ActivatingView { ViewModel = vm }; using (Assert.Multiple()) { await Assert.That(vm.IsActiveCount).IsEqualTo(0); @@ -210,18 +218,14 @@ public async Task CanUnloadAndLoadViewAgain() await Assert.That(fixture.IsActiveCount).IsEqualTo(1); } - fixture.Unloaded.OnNext(Unit.Default); - using (Assert.Multiple()) - { - await Assert.That(vm.IsActiveCount).IsEqualTo(0); - await Assert.That(fixture.IsActiveCount).IsEqualTo(0); - } + var newVm = new ActivatingViewModel(); + await Assert.That(newVm.IsActiveCount).IsEqualTo(0); - fixture.Loaded.OnNext(Unit.Default); + fixture.ViewModel = newVm; using (Assert.Multiple()) { - await Assert.That(vm.IsActiveCount).IsEqualTo(1); - await Assert.That(fixture.IsActiveCount).IsEqualTo(1); + await Assert.That(vm.IsActiveCount).IsEqualTo(0); + await Assert.That(newVm.IsActiveCount).IsEqualTo(1); } } } diff --git a/src/tests/ReactiveUI.Tests/Activation/CanActivateViewFetcherTests.cs b/src/tests/ReactiveUI.Tests/Activation/CanActivateViewFetcherTests.cs index e874baa556..3325b01739 100644 --- a/src/tests/ReactiveUI.Tests/Activation/CanActivateViewFetcherTests.cs +++ b/src/tests/ReactiveUI.Tests/Activation/CanActivateViewFetcherTests.cs @@ -8,35 +8,59 @@ namespace ReactiveUI.Tests.Activation; public class CanActivateViewFetcherTests { [Test] - public async Task GetAffinityForView_WithICanActivate_Returns10() + public async Task GetActivationForView_ActivateDeactivateCycle_EmitsCorrectSequence() { var fetcher = new CanActivateViewFetcher(); - var affinity = fetcher.GetAffinityForView(typeof(ICanActivate)); - await Assert.That(affinity).IsEqualTo(10); - } + var view = new TestCanActivateView(); + var results = new List(); - [Test] - public async Task GetAffinityForView_WithICanActivateDerivative_Returns10() - { - var fetcher = new CanActivateViewFetcher(); - var affinity = fetcher.GetAffinityForView(typeof(TestCanActivateView)); - await Assert.That(affinity).IsEqualTo(10); + var activation = fetcher.GetActivationForView(view).ObserveOn(ImmediateScheduler.Instance); + using var subscription = activation.Subscribe(results.Add); + + view.Activate(); + view.Deactivate(); + view.Activate(); + + await Assert.That(results.Count).IsEqualTo(3); + await Assert.That(results[0]).IsTrue(); + await Assert.That(results[1]).IsFalse(); + await Assert.That(results[2]).IsTrue(); } [Test] - public async Task GetAffinityForView_WithNonICanActivate_Returns0() + public async Task GetActivationForView_MultipleActivations_EmitsEachTime() { var fetcher = new CanActivateViewFetcher(); - var affinity = fetcher.GetAffinityForView(typeof(string)); - await Assert.That(affinity).IsEqualTo(0); + var view = new TestCanActivateView(); + var results = new List(); + + var activation = fetcher.GetActivationForView(view).ObserveOn(ImmediateScheduler.Instance); + using var subscription = activation.Subscribe(results.Add); + + view.Activate(); + view.Activate(); + view.Activate(); + + await Assert.That(results.Count).IsEqualTo(3); + await Assert.That(results).IsEquivalentTo([true, true, true]); } [Test] - public async Task GetAffinityForView_WithNonActivatableView_Returns0() + public async Task GetActivationForView_MultipleDeactivations_EmitsEachTime() { var fetcher = new CanActivateViewFetcher(); - var affinity = fetcher.GetAffinityForView(typeof(TestNonActivatableView)); - await Assert.That(affinity).IsEqualTo(0); + var view = new TestCanActivateView(); + var results = new List(); + + var activation = fetcher.GetActivationForView(view).ObserveOn(ImmediateScheduler.Instance); + using var subscription = activation.Subscribe(results.Add); + + view.Deactivate(); + view.Deactivate(); + view.Deactivate(); + + await Assert.That(results.Count).IsEqualTo(3); + await Assert.That(results).IsEquivalentTo([false, false, false]); } [Test] @@ -83,59 +107,35 @@ public async Task GetActivationForView_WithNonICanActivate_ReturnsFalse() } [Test] - public async Task GetActivationForView_ActivateDeactivateCycle_EmitsCorrectSequence() + public async Task GetAffinityForView_WithICanActivate_Returns10() { var fetcher = new CanActivateViewFetcher(); - var view = new TestCanActivateView(); - var results = new List(); - - var activation = fetcher.GetActivationForView(view).ObserveOn(ImmediateScheduler.Instance); - using var subscription = activation.Subscribe(results.Add); - - view.Activate(); - view.Deactivate(); - view.Activate(); - - await Assert.That(results.Count).IsEqualTo(3); - await Assert.That(results[0]).IsTrue(); - await Assert.That(results[1]).IsFalse(); - await Assert.That(results[2]).IsTrue(); + var affinity = fetcher.GetAffinityForView(typeof(ICanActivate)); + await Assert.That(affinity).IsEqualTo(10); } [Test] - public async Task GetActivationForView_MultipleActivations_EmitsEachTime() + public async Task GetAffinityForView_WithICanActivateDerivative_Returns10() { var fetcher = new CanActivateViewFetcher(); - var view = new TestCanActivateView(); - var results = new List(); - - var activation = fetcher.GetActivationForView(view).ObserveOn(ImmediateScheduler.Instance); - using var subscription = activation.Subscribe(results.Add); - - view.Activate(); - view.Activate(); - view.Activate(); - - await Assert.That(results.Count).IsEqualTo(3); - await Assert.That(results).IsEquivalentTo([true, true, true]); + var affinity = fetcher.GetAffinityForView(typeof(TestCanActivateView)); + await Assert.That(affinity).IsEqualTo(10); } [Test] - public async Task GetActivationForView_MultipleDeactivations_EmitsEachTime() + public async Task GetAffinityForView_WithNonActivatableView_Returns0() { var fetcher = new CanActivateViewFetcher(); - var view = new TestCanActivateView(); - var results = new List(); - - var activation = fetcher.GetActivationForView(view).ObserveOn(ImmediateScheduler.Instance); - using var subscription = activation.Subscribe(results.Add); - - view.Deactivate(); - view.Deactivate(); - view.Deactivate(); + var affinity = fetcher.GetAffinityForView(typeof(TestNonActivatableView)); + await Assert.That(affinity).IsEqualTo(0); + } - await Assert.That(results.Count).IsEqualTo(3); - await Assert.That(results).IsEquivalentTo([false, false, false]); + [Test] + public async Task GetAffinityForView_WithNonICanActivate_Returns0() + { + var fetcher = new CanActivateViewFetcher(); + var affinity = fetcher.GetAffinityForView(typeof(string)); + await Assert.That(affinity).IsEqualTo(0); } private class TestCanActivateView : ReactiveObject, IViewFor, ICanActivate diff --git a/src/tests/ReactiveUI.Tests/Activation/DerivedActivatingViewModel.cs b/src/tests/ReactiveUI.Tests/Activation/DerivedActivatingViewModel.cs index 5505b326c9..927408d7b1 100644 --- a/src/tests/ReactiveUI.Tests/Activation/DerivedActivatingViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Activation/DerivedActivatingViewModel.cs @@ -3,15 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Activation; /// -/// A activating view model which is derived from another ActivatingViewModel. +/// A activating view model which is derived from another ActivatingViewModel. /// public class DerivedActivatingViewModel : ActivatingViewModel { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public DerivedActivatingViewModel() => this.WhenActivated(d => @@ -21,7 +21,7 @@ public DerivedActivatingViewModel() => }); /// - /// Gets or sets the active count. + /// Gets or sets the active count. /// public int IsActiveCountAlso { get; protected set; } } diff --git a/src/tests/ReactiveUI.Tests/Activation/ViewModelActivatorTests.cs b/src/tests/ReactiveUI.Tests/Activation/ViewModelActivatorTests.cs index 95166bff73..479bfd0b67 100644 --- a/src/tests/ReactiveUI.Tests/Activation/ViewModelActivatorTests.cs +++ b/src/tests/ReactiveUI.Tests/Activation/ViewModelActivatorTests.cs @@ -5,19 +5,20 @@ using DynamicData; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Activation; public class ViewModelActivatorTests { /// - /// Tests the activating ticks activated observable. + /// Tests the activating ticks activated observable. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task TestActivatingTicksActivatedObservable() { var viewModelActivator = new ViewModelActivator(); - viewModelActivator.Activated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var activated).Subscribe(); + viewModelActivator.Activated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var activated) + .Subscribe(); viewModelActivator.Activate(); @@ -25,61 +26,66 @@ public async Task TestActivatingTicksActivatedObservable() } /// - /// Tests the deactivating ignoring reference count ticks deactivated observable. + /// Tests the deactivating count doesnt tick deactivated observable. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TestDeactivatingIgnoringRefCountTicksDeactivatedObservable() + public async Task TestDeactivatingCountDoesntTickDeactivatedObservable() { var viewModelActivator = new ViewModelActivator(); - viewModelActivator.Deactivated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var deactivated).Subscribe(); + viewModelActivator.Deactivated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var deactivated) + .Subscribe(); - viewModelActivator.Deactivate(true); + viewModelActivator.Deactivate(); - await Assert.That(deactivated).Count().IsEqualTo(1); + await Assert.That(deactivated).IsEmpty(); } /// - /// Tests the deactivating count doesnt tick deactivated observable. + /// Tests the deactivating following activating ticks deactivated observable. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TestDeactivatingCountDoesntTickDeactivatedObservable() + public async Task TestDeactivatingFollowingActivatingTicksDeactivatedObservable() { var viewModelActivator = new ViewModelActivator(); - viewModelActivator.Deactivated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var deactivated).Subscribe(); + viewModelActivator.Deactivated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var deactivated) + .Subscribe(); - viewModelActivator.Deactivate(false); + viewModelActivator.Activate(); + viewModelActivator.Deactivate(); - await Assert.That(deactivated).IsEmpty(); + await Assert.That(deactivated).Count().IsEqualTo(1); } /// - /// Tests the deactivating following activating ticks deactivated observable. + /// Tests the deactivating ignoring reference count ticks deactivated observable. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TestDeactivatingFollowingActivatingTicksDeactivatedObservable() + public async Task TestDeactivatingIgnoringRefCountTicksDeactivatedObservable() { var viewModelActivator = new ViewModelActivator(); - viewModelActivator.Deactivated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var deactivated).Subscribe(); + viewModelActivator.Deactivated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var deactivated) + .Subscribe(); - viewModelActivator.Activate(); - viewModelActivator.Deactivate(false); + viewModelActivator.Deactivate(true); await Assert.That(deactivated).Count().IsEqualTo(1); } /// - /// Tests the disposing after activation deactivates view model. + /// Tests the disposing after activation deactivates view model. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task TestDisposingAfterActivationDeactivatesViewModel() { var viewModelActivator = new ViewModelActivator(); - viewModelActivator.Activated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var activated).Subscribe(); - viewModelActivator.Deactivated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var deactivated).Subscribe(); + viewModelActivator.Activated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var activated) + .Subscribe(); + viewModelActivator.Deactivated.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var deactivated) + .Subscribe(); using (viewModelActivator.Activate()) using (Assert.Multiple()) diff --git a/src/tests/ReactiveUI.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.Tests/AssemblyHooks.cs index 74fccab79c..efae2b0d49 100644 --- a/src/tests/ReactiveUI.Tests/AssemblyHooks.cs +++ b/src/tests/ReactiveUI.Tests/AssemblyHooks.cs @@ -3,29 +3,48 @@ // 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; -using Splat; -using TUnit.Core; +using ReactiveUI.Builder; +using ReactiveUI.Tests.Utilities.AppBuilder; + +[assembly: NotInParallel] +[assembly: TestExecutor] namespace ReactiveUI.Tests; /// -/// Assembly-level hooks for test initialization and cleanup. +/// Assembly-level hooks for test initialization and cleanup. /// public static class AssemblyHooks { /// - /// Called before any tests in this assembly start. + /// Called before any tests in this assembly start. /// - [Before(HookType.Assembly)] + [Before(Assembly)] public static void AssemblySetup() { // Override ModeDetector to ensure we're detected as being in a unit test runner ModeDetector.OverrideModeDetector(new TestModeDetector()); + + // Initialize ReactiveUI with core services + RxAppBuilder.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); + } + + /// + /// Called after all tests in this assembly complete. + /// + [After(Assembly)] + public static void AssemblyTeardown() + { + // Clean up resources + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); } /// - /// Mode detector that always indicates we're in a unit test runner. + /// Mode detector that always indicates we're in a unit test runner. /// private sealed class TestModeDetector : IModeDetector { diff --git a/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTest.cs b/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTest.cs new file mode 100644 index 0000000000..0b75d00680 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTest.cs @@ -0,0 +1,277 @@ +// Copyright (c) 2025 .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 DynamicData.Binding; +using ReactiveUI.Tests.ReactiveObjects.Mocks; + +namespace ReactiveUI.Tests.AutoPersist; + +/// +/// Comprehensive test suite for AutoPersistCollection functionality. +/// Tests cover collection lifecycle, throttling, and disposal behavior. +/// +[NotInParallel] +public class AutoPersistCollectionTest +{ + /// + /// Tests that disposing AutoPersistCollection stops all persistence operations. + /// Verifies that no saves occur after disposal, even when items change or are added/removed. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_Dispose_StopsAllPersistence() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var manualSave = new Subject(); + var item = new TestFixture(); + var fixture = new ObservableCollectionExtended { item }; + var timesSaved = 0; + + var disp = fixture.AutoPersistCollection( + _ => + { + timesSaved++; + return Observables.Unit; + }, + manualSave, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + await Assert.That(timesSaved).IsEqualTo(0); + + item.IsNotNullString = "Foo"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + + disp.Dispose(); + + fixture.Clear(); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + + item.IsNotNullString = "Bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + + fixture.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + item.IsNotNullString = "Baz"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + + fixture.SuspendNotifications().Dispose(); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + item.IsNotNullString = "Bamf"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + + fixture.RemoveAt(0); + item.IsNotNullString = "Blomf"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + } + + /// + /// Tests that AutoPersistCollection handles duplicate adds correctly. + /// Verifies that adding the same item twice doesn't create duplicate persistence subscriptions. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_DuplicateAdd_NoDoubleSave() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var item = new TestFixture(); + var fixture = new ObservableCollection(); + var timesSaved = 0; + + fixture.AutoPersistCollection( + _ => + { + timesSaved++; + return Observables.Unit; + }, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + fixture.Add(item); + fixture.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + item.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + + await Assert.That(timesSaved).IsEqualTo(1); + } + + /// + /// Tests the complete lifecycle of AutoPersistCollection with add, remove, and re-add operations. + /// Verifies that persistence is enabled for items in the collection and disabled when removed. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_Lifecycle_ManagesPersistence() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var manualSave = new Subject(); + var item = new TestFixture(); + var fixture = new ObservableCollectionExtended { item }; + var timesSaved = 0; + + fixture.AutoPersistCollection( + _ => + { + timesSaved++; + return Observables.Unit; + }, + manualSave, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + await Assert.That(timesSaved).IsEqualTo(0); + + item.IsNotNullString = "Foo"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + + fixture.Clear(); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + item.IsNotNullString = "Bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + + fixture.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + item.IsNotNullString = "Baz"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(2); + + fixture.SuspendNotifications().Dispose(); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + item.IsNotNullString = "Bamf"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(3); + + fixture.RemoveAt(0); + item.IsNotNullString = "Blomf"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(3); + } + + /// + /// Tests that AutoPersistCollection manual save signal triggers immediate save. + /// Verifies manual save functionality works in addition to automatic throttled saves. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_ManualSave_TriggersImmediateSave() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var manualSave = new Subject(); + var item = new TestFixture(); + var fixture = new ObservableCollection { item }; + var timesSaved = 0; + + fixture.AutoPersistCollection( + _ => + { + timesSaved++; + return Observables.Unit; + }, + manualSave, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + await Assert.That(timesSaved).IsEqualTo(0); + + manualSave.OnNext(Unit.Default); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + } + + /// + /// Tests that AutoPersistCollection with metadata provider works correctly. + /// Verifies that metadata provider is called for each item and persistence works. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_MetadataProvider_WorksCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var item = new TestFixture(); + var fixture = new ObservableCollection { item }; + var metadataProvider = AutoPersistHelper.CreateMetadataProvider(); + var timesSaved = 0; + + fixture.AutoPersistCollection( + _ => + { + timesSaved++; + return Observables.Unit; + }, + Observable.Never, + metadataProvider, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + item.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + } + + /// + /// Tests that AutoPersistCollection handles collection reset events correctly. + /// Verifies that persistence continues after a reset operation. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_Reset_ContinuesPersistence() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var item = new TestFixture(); + var fixture = new ObservableCollectionExtended { item }; + var timesSaved = 0; + + fixture.AutoPersistCollection( + _ => + { + timesSaved++; + return Observables.Unit; + }, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + item.IsNotNullString = "Before"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(1); + + fixture.SuspendNotifications().Dispose(); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + item.IsNotNullString = "After"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(110)); + await Assert.That(timesSaved).IsEqualTo(2); + } +} diff --git a/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTests.cs b/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTests.cs deleted file mode 100644 index 12e97a9755..0000000000 --- a/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTests.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2025 .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 DynamicData.Binding; - -using Microsoft.Reactive.Testing; - -using ReactiveUI.Testing; - -namespace ReactiveUI.Tests; - -public class AutoPersistCollectionTests -{ - /// - /// Test the automatic persist collection smoke test. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AutoPersistCollectionSmokeTest() => - await new TestScheduler().With(async scheduler => - { - var manualSave = new Subject(); - - var item = new TestFixture(); - var fixture = new ObservableCollectionExtended { item }; - - var timesSaved = 0; - fixture.AutoPersistCollection( - _ => - { - timesSaved++; - return Observables.Unit; - }, - manualSave, - TimeSpan.FromMilliseconds(100)); - - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(0); - - // By being added to collection, AutoPersist is enabled for item - item.IsNotNullString = "Foo"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Removed from collection = no save - fixture.Clear(); - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Item isn't in the collection, it doesn't get persisted anymore - item.IsNotNullString = "Bar"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Added back item gets saved - fixture.Add(item); - scheduler.AdvanceByMs(100); // Compensate for scheduling - item.IsNotNullString = "Baz"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(2); - - // Even if we issue a reset - fixture.SuspendNotifications().Dispose(); // Will cause a reset. - - scheduler.AdvanceByMs(100); // Compensate for scheduling - item.IsNotNullString = "Bamf"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(3); - - // Remove by hand = no save - fixture.RemoveAt(0); - item.IsNotNullString = "Blomf"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(3); - }); - - /// - /// Test the automatic persist collection disconnects on dispose. - /// - /// A representing the asynchronous operation. - [Test] - public async Task AutoPersistCollectionDisconnectsOnDispose() => - await new TestScheduler().With(async scheduler => - { - var manualSave = new Subject(); - - var item = new TestFixture(); - var fixture = new ObservableCollectionExtended { item }; - - var timesSaved = 0; - var disp = fixture.AutoPersistCollection( - _ => - { - timesSaved++; - return Observables.Unit; - }, - manualSave, - TimeSpan.FromMilliseconds(100)); - - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(0); - - // By being added to collection, AutoPersist is enabled for item - item.IsNotNullString = "Foo"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Dispose = no save - disp.Dispose(); - - // Removed from collection = no save - fixture.Clear(); - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Item isn't in the collection, it doesn't get persisted anymore - item.IsNotNullString = "Bar"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Added back item + dispose = no save - fixture.Add(item); - scheduler.AdvanceByMs(100); // Compensate for scheduling - item.IsNotNullString = "Baz"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Even if we issue a reset, no save - fixture.SuspendNotifications().Dispose(); // Will trigger a reset. - scheduler.AdvanceByMs(100); // Compensate for scheduling - item.IsNotNullString = "Bamf"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - - // Remove by hand = no save - fixture.RemoveAt(0); - item.IsNotNullString = "Blomf"; - scheduler.AdvanceByMs(2 * 100); - await Assert.That(timesSaved).IsEqualTo(1); - }); -} diff --git a/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistHelperTest.cs b/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistHelperTest.cs new file mode 100644 index 0000000000..6236c01a02 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistHelperTest.cs @@ -0,0 +1,735 @@ +// Copyright (c) 2025 .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 ReactiveUI.Tests.ReactiveObjects.Mocks; + +namespace ReactiveUI.Tests.AutoPersist; + +/// +/// Comprehensive test suite for AutoPersistHelper. +/// Tests cover all overloads, throttling, scheduling, and collection behavior. +/// +[NotInParallel] +[TestExecutor] +public class AutoPersistHelperTest +{ + /// + /// Tests that ActOnEveryObject calls onAdd when new item added. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_AddNewItem_CallsOnAdd() + { + var collection = new ObservableCollection(); + var addedItems = new List(); + + collection.ActOnEveryObject( + addedItems.Add, + _ => { }); + + var item = new TestFixture(); + collection.Add(item); + + using (Assert.Multiple()) + { + await Assert.That(addedItems).Count().IsEqualTo(1); + await Assert.That(addedItems[0]).IsEqualTo(item); + } + } + + /// + /// Tests that ActOnEveryObject handles collection Clear. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_ClearCollection_CallsOnRemove() + { + var item1 = new TestFixture(); + var item2 = new TestFixture(); + var collection = new ObservableCollection { item1, item2 }; + var removedItems = new List(); + + collection.ActOnEveryObject( + _ => { }, + removedItems.Add); + + collection.Clear(); + + using (Assert.Multiple()) + { + await Assert.That(removedItems).Count().IsEqualTo(2); + await Assert.That(removedItems).Contains(item1); + await Assert.That(removedItems).Contains(item2); + } + } + + /// + /// Tests that ActOnEveryObject calls onRemove on disposal. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_Dispose_CallsOnRemoveForAll() + { + var item1 = new TestFixture(); + var item2 = new TestFixture(); + var collection = new ObservableCollection { item1, item2 }; + var removedItems = new List(); + + var subscription = collection.ActOnEveryObject( + _ => { }, + removedItems.Add); + + subscription.Dispose(); + + using (Assert.Multiple()) + { + await Assert.That(removedItems).Count().IsEqualTo(2); + await Assert.That(removedItems).Contains(item1); + await Assert.That(removedItems).Contains(item2); + } + } + + /// + /// Tests that ActOnEveryObject calls onAdd for existing items. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_ExistingItems_CallsOnAdd() + { + var item1 = new TestFixture(); + var item2 = new TestFixture(); + var collection = new ObservableCollection { item1, item2 }; + var addedItems = new List(); + + collection.ActOnEveryObject( + addedItems.Add, + _ => { }); + + using (Assert.Multiple()) + { + await Assert.That(addedItems).Count().IsEqualTo(2); + await Assert.That(addedItems).Contains(item1); + await Assert.That(addedItems).Contains(item2); + } + } + + /// + /// Tests that ActOnEveryObject throws on null collection. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_NullCollection_ThrowsArgumentNullException() + { + ObservableCollection? collection = null; + + await Assert.ThrowsAsync(async () => + { + collection!.ActOnEveryObject(_ => { }, _ => { }); + await Task.CompletedTask; + }); + } + + /// + /// Tests that ActOnEveryObject throws on null onAdd. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_NullOnAdd_ThrowsArgumentNullException() + { + var collection = new ObservableCollection(); + + await Assert.ThrowsAsync(async () => + { + collection.ActOnEveryObject(null!, _ => { }); + await Task.CompletedTask; + }); + } + + /// + /// Tests that ActOnEveryObject throws on null onRemove. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_NullOnRemove_ThrowsArgumentNullException() + { + var collection = new ObservableCollection(); + + await Assert.ThrowsAsync(async () => + { + collection.ActOnEveryObject(_ => { }, null!); + await Task.CompletedTask; + }); + } + + /// + /// Tests that ActOnEveryObject works with ReadOnlyObservableCollection. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_ReadOnlyCollection_WorksCorrectly() + { + var innerCollection = new ObservableCollection(); + var readOnlyCollection = new ReadOnlyObservableCollection(innerCollection); + var addedItems = new List(); + + readOnlyCollection.ActOnEveryObject( + addedItems.Add, + _ => { }); + + var item = new TestFixture(); + innerCollection.Add(item); + + using (Assert.Multiple()) + { + await Assert.That(addedItems).Count().IsEqualTo(1); + await Assert.That(addedItems[0]).IsEqualTo(item); + } + } + + /// + /// Tests that ActOnEveryObject calls onRemove when item removed. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_RemoveItem_CallsOnRemove() + { + var item = new TestFixture(); + var collection = new ObservableCollection { item }; + var removedItems = new List(); + + collection.ActOnEveryObject( + _ => { }, + removedItems.Add); + + collection.Remove(item); + + using (Assert.Multiple()) + { + await Assert.That(removedItems).Count().IsEqualTo(1); + await Assert.That(removedItems[0]).IsEqualTo(item); + } + } + + /// + /// Tests that ActOnEveryObject handles collection Replace. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_ReplaceItem_CallsOnRemoveAndOnAdd() + { + var item1 = new TestFixture(); + var item2 = new TestFixture(); + var collection = new ObservableCollection { item1 }; + var addedItems = new List(); + var removedItems = new List(); + + collection.ActOnEveryObject( + addedItems.Add, + removedItems.Add); + + collection[0] = item2; + + using (Assert.Multiple()) + { + await Assert.That(addedItems).Count().IsEqualTo(2); + await Assert.That(addedItems[0]).IsEqualTo(item1); + await Assert.That(addedItems[1]).IsEqualTo(item2); + await Assert.That(removedItems).Count().IsEqualTo(1); + await Assert.That(removedItems[0]).IsEqualTo(item1); + } + } + + /// + /// Tests that AutoPersist disposes correctly. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_Dispose_StopsSaving() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var fixture = new TestFixture(); + var saveCount = 0; + + var subscription = fixture.AutoPersist( + _ => + { + saveCount++; + return Observables.Unit; + }, + TimeSpan.FromSeconds(1)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + fixture.IsNotNullString = "First"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + + await Assert.That(saveCount).IsEqualTo(1); + + subscription.Dispose(); + + fixture.IsNotNullString = "Second"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(2)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that manual save signal triggers immediate save. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_ManualSaveSignal_TriggersSave() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var fixture = new TestFixture(); + var manualSave = new Subject(); + var saveCount = 0; + + fixture.AutoPersist( + _ => + { + saveCount++; + return Observables.Unit; + }, + manualSave, + TimeSpan.FromSeconds(1)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + manualSave.OnNext(Unit.Default); + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that AutoPersist with metadata provider works for collections. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_MetadataProvider_WorksCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var metadataProvider = AutoPersistHelper.CreateMetadataProvider(); + var fixture = new TestFixture(); + var metadata = metadataProvider(fixture); + + await Assert.That(metadata).IsNotNull(); + await Assert.That(metadata.HasDataContract).IsTrue(); + await Assert.That(metadata.PersistablePropertyNames).Contains("IsNotNullString"); + } + + /// + /// Tests that AutoPersist throws for objects without DataContract attribute. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task AutoPersist_NoDataContract_ThrowsArgumentException() + { + var obj = new ObjectWithoutDataContract(); + + await Assert.ThrowsAsync(async () => + { + obj.AutoPersist(_ => Observables.Unit); + await Task.CompletedTask; + }); + } + + /// + /// Tests that AutoPersist only saves for DataMember properties. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_NonDataMemberProperty_DoesNotTriggerSave() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var fixture = new TestFixture(); + var saveCount = 0; + + fixture.AutoPersist( + _ => + { + saveCount++; + return Observables.Unit; + }, + TimeSpan.FromSeconds(1)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + fixture.PocoProperty = "NoSave"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(2)); + + await Assert.That(saveCount).IsEqualTo(0); + } + + /// + /// Tests that AutoPersist throws on null doPersist. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task AutoPersist_NullDoPersist_ThrowsArgumentNullException() + { + var fixture = new TestFixture(); + + await Assert.ThrowsAsync(async () => + { + fixture.AutoPersist(null!); + await Task.CompletedTask; + }); + } + + /// + /// Tests that AutoPersist throws on null manualSaveSignal. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task AutoPersist_NullManualSaveSignal_ThrowsArgumentNullException() + { + var fixture = new TestFixture(); + + await Assert.ThrowsAsync(async () => + { + fixture.AutoPersist(_ => Observables.Unit, null!, TimeSpan.FromSeconds(1)); + await Task.CompletedTask; + }); + } + + /// + /// Tests that AutoPersist with metadata throws on null metadata. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task AutoPersist_NullMetadata_ThrowsArgumentNullException() + { + var fixture = new TestFixture(); + + await Assert.ThrowsAsync(async () => + { + fixture.AutoPersist(_ => Observables.Unit, null!, TimeSpan.FromSeconds(1)); + await Task.CompletedTask; + }); + } + + /// + /// Tests that AutoPersist saves when a DataMember property changes. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_PropertyChange_TriggersSave() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var fixture = new TestFixture(); + var saveCount = 0; + + fixture.AutoPersist( + _ => + { + saveCount++; + return Observables.Unit; + }, + TimeSpan.FromSeconds(1)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + fixture.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that AutoPersist respects throttle interval. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_Throttle_RespectInterval() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var fixture = new TestFixture(); + var saveCount = 0; + + fixture.AutoPersist( + _ => + { + saveCount++; + return Observables.Unit; + }, + TimeSpan.FromSeconds(2)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + fixture.IsNotNullString = "First"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + await Assert.That(saveCount).IsEqualTo(0); + + fixture.IsNotNullString = "Second"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + await Assert.That(saveCount).IsEqualTo(0); + + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that AutoPersist with metadata works correctly. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_WithMetadata_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var fixture = new TestFixture(); + var metadata = AutoPersistHelper.CreateMetadata(); + var saveCount = 0; + + fixture.AutoPersist( + _ => + { + saveCount++; + return Observables.Unit; + }, + metadata, + TimeSpan.FromSeconds(1)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + fixture.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that AutoPersistCollection adds persistence to collection items. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_AddItem_EnablesPersistence() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var collection = new ObservableCollection(); + var saveCount = 0; + + collection.AutoPersistCollection( + _ => + { + saveCount++; + return Observables.Unit; + }, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + var item = new TestFixture(); + collection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + item.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(150)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that AutoPersistCollection throws on null collection. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task AutoPersistCollection_NullCollection_ThrowsArgumentNullException() + { + ObservableCollection? collection = null; + + await Assert.ThrowsAsync(async () => + { + collection!.AutoPersistCollection(_ => Observables.Unit); + await Task.CompletedTask; + }); + } + + /// + /// Tests that AutoPersistCollection throws on null doPersist. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task AutoPersistCollection_NullDoPersist_ThrowsArgumentNullException() + { + var collection = new ObservableCollection(); + + await Assert.ThrowsAsync(async () => + { + collection.AutoPersistCollection(null!); + await Task.CompletedTask; + }); + } + + /// + /// Tests that AutoPersistCollection throws on null metadata. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task AutoPersistCollection_NullMetadata_ThrowsArgumentNullException() + { + var collection = new ObservableCollection(); + + await Assert.ThrowsAsync(async () => + { + collection.AutoPersistCollection(_ => Observables.Unit, (AutoPersistHelper.AutoPersistMetadata)null!); + await Task.CompletedTask; + }); + } + + /// + /// Tests that AutoPersistCollection works with ReadOnlyObservableCollection. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_ReadOnlyCollection_WorksCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var innerCollection = new ObservableCollection(); + var readOnlyCollection = new ReadOnlyObservableCollection(innerCollection); + var saveCount = 0; + var manualSave = new Subject(); + + readOnlyCollection.AutoPersistCollection( + _ => + { + saveCount++; + return Observables.Unit; + }, + manualSave, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + var item = new TestFixture(); + innerCollection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + item.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(150)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that AutoPersistCollection removes persistence when item removed. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_RemoveItem_DisablesPersistence() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var item = new TestFixture(); + var collection = new ObservableCollection { item }; + var saveCount = 0; + + collection.AutoPersistCollection( + _ => + { + saveCount++; + return Observables.Unit; + }, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + collection.Remove(item); + + item.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(150)); + + await Assert.That(saveCount).IsEqualTo(0); + } + + /// + /// Tests that AutoPersistCollection with metadata works correctly. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_WithMetadata_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var collection = new ObservableCollection(); + var metadata = AutoPersistHelper.CreateMetadata(); + var saveCount = 0; + + collection.AutoPersistCollection( + _ => + { + saveCount++; + return Observables.Unit; + }, + metadata, + TimeSpan.FromMilliseconds(100)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10)); + + var item = new TestFixture(); + collection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + item.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(150)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that CreateMetadata returns correct metadata. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task CreateMetadata_ReturnsCorrectMetadata() + { + var metadata = AutoPersistHelper.CreateMetadata(); + + using (Assert.Multiple()) + { + await Assert.That(metadata).IsNotNull(); + await Assert.That(metadata.HasDataContract).IsTrue(); + await Assert.That(metadata.PersistablePropertyNames).Contains("IsNotNullString"); + await Assert.That(metadata.PersistablePropertyNames).Contains("IsOnlyOneWord"); + await Assert.That(metadata.PersistablePropertyNames).DoesNotContain("PocoProperty"); + } + } + + /// + /// Test object without DataContract attribute. + /// + private class ObjectWithoutDataContract : ReactiveObject + { + private string? _property; + + /// + /// Gets or sets a test property. + /// + public string? Property + { + get => _property; + set => this.RaiseAndSetIfChanged(ref _property, value); + } + } +} diff --git a/src/tests/ReactiveUI.Tests/Binding/ComponentModelTypeConverterTest.cs b/src/tests/ReactiveUI.Tests/Binding/ComponentModelTypeConverterTest.cs deleted file mode 100644 index 3d48dea9d0..0000000000 --- a/src/tests/ReactiveUI.Tests/Binding/ComponentModelTypeConverterTest.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) 2025 .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.Binding; - -/// -/// Tests for . -/// -public class ComponentModelTypeConverterTest -{ - /// - /// Tests that GetAffinityForObjects returns correct affinity for types with TypeConverter. - /// - /// A representing the asynchronous operation. - [Test] - public async Task GetAffinityForObjects_WithTypeConverter_Returns10() - { - var converter = new ComponentModelTypeConverter(); - - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(int)); - - await Assert.That(affinity).IsEqualTo(10); - } - - /// - /// Tests that GetAffinityForObjects returns zero for types without converter. - /// - /// A representing the asynchronous operation. - [Test] - public async Task GetAffinityForObjects_WithoutTypeConverter_Returns0() - { - var converter = new ComponentModelTypeConverter(); - - // Object to StringBuilder has no standard converter - var affinity = converter.GetAffinityForObjects(typeof(object), typeof(System.Text.StringBuilder)); - - await Assert.That(affinity).IsEqualTo(0); - } - - /// - /// Tests that TryConvert converts string to int successfully. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_StringToInt_ConvertsSuccessfully() - { - var converter = new ComponentModelTypeConverter(); - - var success = converter.TryConvert("42", typeof(int), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(42); - } - - /// - /// Tests that TryConvert converts int to string successfully. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_IntToString_ConvertsSuccessfully() - { - var converter = new ComponentModelTypeConverter(); - - var success = converter.TryConvert(42, typeof(string), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo("42"); - } - - /// - /// Tests that TryConvert handles null values correctly. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_NullValue_ReturnsNullAndTrue() - { - var converter = new ComponentModelTypeConverter(); - - var success = converter.TryConvert(null, typeof(int), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsNull(); - } - - /// - /// Tests that TryConvert handles format exceptions gracefully. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_InvalidFormat_ReturnsFalse() - { - var converter = new ComponentModelTypeConverter(); - - var success = converter.TryConvert("not a number", typeof(int), null, out var result); - - await Assert.That(success).IsFalse(); - await Assert.That(result).IsNull(); - } - - /// - /// Tests that TryConvert handles empty strings gracefully. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_EmptyString_ReturnsFalse() - { - var converter = new ComponentModelTypeConverter(); - - var success = converter.TryConvert(string.Empty, typeof(int), null, out var result); - - await Assert.That(success).IsFalse(); - await Assert.That(result).IsNull(); - } - - /// - /// Tests that TryConvert throws exception for incompatible types. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_IncompatibleTypes_Throws() - { - var converter = new ComponentModelTypeConverter(); - - await Assert.That(() => converter.TryConvert(new object(), typeof(System.Text.StringBuilder), null, out _)) - .Throws(); - } - - /// - /// Tests that TryConvert converts string to double successfully. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_StringToDouble_ConvertsSuccessfully() - { - var converter = new ComponentModelTypeConverter(); - - var success = converter.TryConvert("3.14", typeof(double), null, out var result); - - await Assert.That(success).IsTrue(); - await Assert.That(result).IsEqualTo(3.14); - } - - /// - /// Tests that TryConvert converts string to bool successfully. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryConvert_StringToBool_ConvertsSuccessfully() - { - var converter = new ComponentModelTypeConverter(); - - var successTrue = converter.TryConvert("true", typeof(bool), null, out var resultTrue); - var successFalse = converter.TryConvert("false", typeof(bool), null, out var resultFalse); - - await Assert.That(successTrue).IsTrue(); - await Assert.That(resultTrue).IsEqualTo(true); - await Assert.That(successFalse).IsTrue(); - await Assert.That(resultFalse).IsEqualTo(false); - } - - /// - /// Tests that GetAffinityForObjects caches results. - /// - /// A representing the asynchronous operation. - [Test] - public async Task GetAffinityForObjects_CachesResults() - { - var converter = new ComponentModelTypeConverter(); - - var affinity1 = converter.GetAffinityForObjects(typeof(string), typeof(int)); - var affinity2 = converter.GetAffinityForObjects(typeof(string), typeof(int)); - - await Assert.That(affinity1).IsEqualTo(affinity2); - await Assert.That(affinity1).IsEqualTo(10); - } -} diff --git a/src/tests/ReactiveUI.Tests/BindingTypeConvertersTest.cs b/src/tests/ReactiveUI.Tests/BindingTypeConvertersTest.cs deleted file mode 100644 index 65e3d09503..0000000000 --- a/src/tests/ReactiveUI.Tests/BindingTypeConvertersTest.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2025 .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; - -/// -/// Tests for binding type converters. -/// -public class BindingTypeConvertersTest -{ - /// - /// Tests that equality type converter do reference cast should convert null nullable values. - /// - /// A representing the asynchronous operation. - [Test] - public async Task EqualityTypeConverterDoReferenceCastShouldConvertNullNullableValues() - { - double? nullDouble = null; - double? expected = null; - var result = EqualityTypeConverter.DoReferenceCast(nullDouble, typeof(double?)); - await Assert.That(result).IsEqualTo(expected); - } - - /// - /// Tests that equality type converter do reference cast should convert nullable values. - /// - /// A representing the asynchronous operation. - [Test] - public async Task EqualityTypeConverterDoReferenceCastShouldConvertNullableValues() - { - double? doubleValue = 1.0; - double? expected = 1.0; - var result = EqualityTypeConverter.DoReferenceCast(doubleValue, typeof(double?)); - await Assert.That(result).IsEqualTo(expected); - } - - /// - /// Tests that equality type converter do reference cast should throw when converting from null nullable to value. - /// - [Test] - public void EqualityTypeConverterDoReferenceCastShouldThrowWhenConvertingFromNullNullableToValueType() - { - double? nullDouble = null; - Assert.Throws(() => EqualityTypeConverter.DoReferenceCast(nullDouble, typeof(double))); - } - - /// - /// Tests that equality type converter do reference cast nullable to value. - /// - /// A representing the asynchronous operation. - [Test] - public async Task EqualityTypeConverterDoReferenceCastNullableToValueType() - { - double? doubleValue = 1.0; - double? expected = 1.0; - var result = EqualityTypeConverter.DoReferenceCast(doubleValue, typeof(double)); - await Assert.That(result).IsEqualTo(expected); - } - - /// - /// Tests that equality type converter do reference cast should convert value types. - /// - /// A representing the asynchronous operation. - [Test] - public async Task EqualityTypeConverterDoReferenceCastShouldConvertValueTypes() - { - const double doubleValue = 1.0; - var result = EqualityTypeConverter.DoReferenceCast(doubleValue, typeof(double)); - await Assert.That(result).IsEqualTo(doubleValue); - } -} diff --git a/src/tests/ReactiveUI.Tests/BindingTypeConvertersUnitTests.cs b/src/tests/ReactiveUI.Tests/BindingTypeConvertersUnitTests.cs index 1e5143d520..27d68842c3 100644 --- a/src/tests/ReactiveUI.Tests/BindingTypeConvertersUnitTests.cs +++ b/src/tests/ReactiveUI.Tests/BindingTypeConvertersUnitTests.cs @@ -3,214 +3,163 @@ // 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; -using System.Threading.Tasks; +namespace ReactiveUI.Tests; -namespace ReactiveUI.Tests +public class BindingTypeConvertersUnitTests { - public class BindingTypeConvertersUnitTests + [Test] + public async Task ByteToStringTypeConverter_Converts_Correctly() { - [Test] - public async Task ByteToStringTypeConverter_Converts_Correctly() - { - var converter = new ByteToStringTypeConverter(); - byte val = 123; - object result; - - // Byte to String - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo("123"); - - // String to Byte - await Assert.That(converter.TryConvert("123", typeof(byte), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo((byte)123); - - // Invalid String - await Assert.That(converter.TryConvert("invalid", typeof(byte), null, out result)).IsFalse(); - } - - [Test] - public async Task NullableByteToStringTypeConverter_Converts_Correctly() - { - var converter = new NullableByteToStringTypeConverter(); - byte? val = 123; - object result; - - // Byte? to String - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo("123"); - - // String to Byte? - await Assert.That(converter.TryConvert("123", typeof(byte?), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo((byte?)123); - } - - [Test] - public async Task ShortToStringTypeConverter_Converts_Correctly() - { - var converter = new ShortToStringTypeConverter(); - short val = 12345; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo("12345"); - - await Assert.That(converter.TryConvert("12345", typeof(short), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo((short)12345); - } - - [Test] - public async Task NullableShortToStringTypeConverter_Converts_Correctly() - { - var converter = new NullableShortToStringTypeConverter(); - short? val = 12345; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo("12345"); - - await Assert.That(converter.TryConvert("12345", typeof(short?), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo((short?)12345); - } - - [Test] - public async Task IntegerToStringTypeConverter_Converts_Correctly() - { - var converter = new IntegerToStringTypeConverter(); - int val = 123456789; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo("123456789"); - - await Assert.That(converter.TryConvert("123456789", typeof(int), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(123456789); - } - - [Test] - public async Task NullableIntegerToStringTypeConverter_Converts_Correctly() - { - var converter = new NullableIntegerToStringTypeConverter(); - int? val = 123456789; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo("123456789"); - - await Assert.That(converter.TryConvert("123456789", typeof(int?), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo((int?)123456789); - } - - [Test] - public async Task LongToStringTypeConverter_Converts_Correctly() - { - var converter = new LongToStringTypeConverter(); - long val = 1234567890123456789; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo("1234567890123456789"); - - await Assert.That(converter.TryConvert("1234567890123456789", typeof(long), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(1234567890123456789); - } - - [Test] - public async Task NullableLongToStringTypeConverter_Converts_Correctly() - { - var converter = new NullableLongToStringTypeConverter(); - long? val = 1234567890123456789; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo("1234567890123456789"); - - await Assert.That(converter.TryConvert("1234567890123456789", typeof(long?), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo((long?)1234567890123456789); - } - - [Test] - public async Task SingleToStringTypeConverter_Converts_Correctly() - { - var converter = new SingleToStringTypeConverter(); - float val = 123.45f; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val.ToString()); - - await Assert.That(converter.TryConvert(val.ToString(), typeof(float), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val); - } - - [Test] - public async Task NullableSingleToStringTypeConverter_Converts_Correctly() - { - var converter = new NullableSingleToStringTypeConverter(); - float? val = 123.45f; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val.ToString()); - - await Assert.That(converter.TryConvert(val.ToString(), typeof(float?), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val); - } - - [Test] - public async Task DoubleToStringTypeConverter_Converts_Correctly() - { - var converter = new DoubleToStringTypeConverter(); - double val = 123.456789; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val.ToString()); - - await Assert.That(converter.TryConvert(val.ToString(), typeof(double), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val); - } - - [Test] - public async Task NullableDoubleToStringTypeConverter_Converts_Correctly() - { - var converter = new NullableDoubleToStringTypeConverter(); - double? val = 123.456789; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val.ToString()); - - await Assert.That(converter.TryConvert(val.ToString(), typeof(double?), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val); - } - - [Test] - public async Task DecimalToStringTypeConverter_Converts_Correctly() - { - var converter = new DecimalToStringTypeConverter(); - decimal val = 123.456m; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val.ToString()); - - await Assert.That(converter.TryConvert(val.ToString(), typeof(decimal), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val); - } - - [Test] - public async Task NullableDecimalToStringTypeConverter_Converts_Correctly() - { - var converter = new NullableDecimalToStringTypeConverter(); - decimal? val = 123.456m; - object result; - - await Assert.That(converter.TryConvert(val, typeof(string), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val.ToString()); - - await Assert.That(converter.TryConvert(val.ToString(), typeof(decimal?), null, out result)).IsTrue(); - await Assert.That(result).IsEqualTo(val); - } + var converter = new ByteToStringTypeConverter(); + byte val = 123; + + // Byte to String + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("123"); + } + + [Test] + public async Task DecimalToStringTypeConverter_Converts_Correctly() + { + var converter = new DecimalToStringTypeConverter(); + var val = 123.456m; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(val.ToString()); + } + + [Test] + public async Task DoubleToStringTypeConverter_Converts_Correctly() + { + var converter = new DoubleToStringTypeConverter(); + var val = 123.456789; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(val.ToString()); + } + + [Test] + public async Task IntegerToStringTypeConverter_Converts_Correctly() + { + var converter = new IntegerToStringTypeConverter(); + var val = 123456789; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("123456789"); + } + + [Test] + public async Task LongToStringTypeConverter_Converts_Correctly() + { + var converter = new LongToStringTypeConverter(); + var val = 1234567890123456789; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("1234567890123456789"); + } + + [Test] + public async Task NullableByteToStringTypeConverter_Converts_Correctly() + { + var converter = new NullableByteToStringTypeConverter(); + byte? val = 123; + + // Byte? to String + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("123"); + } + + [Test] + public async Task NullableDecimalToStringTypeConverter_Converts_Correctly() + { + var converter = new NullableDecimalToStringTypeConverter(); + decimal? val = 123.456m; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(val.ToString()); + } + + [Test] + public async Task NullableDoubleToStringTypeConverter_Converts_Correctly() + { + var converter = new NullableDoubleToStringTypeConverter(); + double? val = 123.456789; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(val.ToString()); + } + + [Test] + public async Task NullableIntegerToStringTypeConverter_Converts_Correctly() + { + var converter = new NullableIntegerToStringTypeConverter(); + int? val = 123456789; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("123456789"); + } + + [Test] + public async Task NullableLongToStringTypeConverter_Converts_Correctly() + { + var converter = new NullableLongToStringTypeConverter(); + long? val = 1234567890123456789; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("1234567890123456789"); + } + + [Test] + public async Task NullableShortToStringTypeConverter_Converts_Correctly() + { + var converter = new NullableShortToStringTypeConverter(); + short? val = 12345; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("12345"); + } + + [Test] + public async Task NullableSingleToStringTypeConverter_Converts_Correctly() + { + var converter = new NullableSingleToStringTypeConverter(); + float? val = 123.45f; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(val.ToString()); + } + + [Test] + public async Task ShortToStringTypeConverter_Converts_Correctly() + { + var converter = new ShortToStringTypeConverter(); + short val = 12345; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("12345"); + } + + [Test] + public async Task SingleToStringTypeConverter_Converts_Correctly() + { + var converter = new SingleToStringTypeConverter(); + var val = 123.45f; + + var result = converter.TryConvert(val, null, out var output); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(val.ToString()); } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaCommandParameterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaCommandParameterTests.cs index 8c798f9fcd..8ce3c13a21 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaCommandParameterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaCommandParameterTests.cs @@ -10,51 +10,41 @@ namespace ReactiveUI.Tests.Bindings.CommandBindings; public class CreatesCommandBindingViaCommandParameterTests { [Test] - public async Task GetAffinityForObject_WithCommandAndCommandParameter_Returns5() + public async Task BindCommandToObject_RestoresOriginalValuesOnDispose() { var binder = new CreatesCommandBindingViaCommandParameter(); - var affinity = binder.GetAffinityForObject(typeof(CommandControl), hasEventTarget: false); - await Assert.That(affinity).IsEqualTo(5); - } + var target = new CommandControl(); + var originalCommand = ReactiveCommand.Create(() => { }); + var originalParameter = "original"; - [Test] - public async Task GetAffinityForObject_WithEventTarget_Returns0() - { - var binder = new CreatesCommandBindingViaCommandParameter(); - var affinity = binder.GetAffinityForObject(typeof(CommandControl), hasEventTarget: true); - await Assert.That(affinity).IsEqualTo(0); - } + target.Command = originalCommand; + target.CommandParameter = originalParameter; - [Test] - public async Task GetAffinityForObject_WithoutCommandProperty_Returns0() - { - var binder = new CreatesCommandBindingViaCommandParameter(); - var affinity = binder.GetAffinityForObject(typeof(string), hasEventTarget: false); - await Assert.That(affinity).IsEqualTo(0); - } + var newCommand = ReactiveCommand.Create(() => { }); + using (var binding = binder.BindCommandToObject(newCommand, target, Observable.Return("new"))) + { + await Assert.That(target.Command).IsEqualTo(newCommand); + await Assert.That(target.CommandParameter).IsEqualTo("new"); + } - [Test] - public async Task GetAffinityForObject_WithOnlyCommandProperty_Returns0() - { - var binder = new CreatesCommandBindingViaCommandParameter(); - var affinity = binder.GetAffinityForObject(typeof(OnlyCommandControl), hasEventTarget: false); - await Assert.That(affinity).IsEqualTo(0); + await Assert.That(target.Command).IsEqualTo(originalCommand); + await Assert.That(target.CommandParameter).IsEqualTo(originalParameter); } [Test] - public async Task GetAffinityForObject_Generic_WithCommandAndCommandParameter_Returns5() + public async Task BindCommandToObject_SetsCommandParameterFromObservable() { var binder = new CreatesCommandBindingViaCommandParameter(); - var affinity = binder.GetAffinityForObject(hasEventTarget: false); - await Assert.That(affinity).IsEqualTo(5); - } + var target = new CommandControl(); + var command = ReactiveCommand.Create(_ => { }); + var parameter = new BehaviorSubject(42); - [Test] - public async Task GetAffinityForObject_Generic_WithEventTarget_Returns0() - { - var binder = new CreatesCommandBindingViaCommandParameter(); - var affinity = binder.GetAffinityForObject(hasEventTarget: true); - await Assert.That(affinity).IsEqualTo(0); + using var binding = binder.BindCommandToObject(command, target, parameter); + + await Assert.That(target.CommandParameter).IsEqualTo(42); + + parameter.OnNext(100); + await Assert.That(target.CommandParameter).IsEqualTo(100); } [Test] @@ -70,51 +60,38 @@ public async Task BindCommandToObject_SetsCommandProperty() } [Test] - public async Task BindCommandToObject_SetsCommandParameterFromObservable() + public async Task BindCommandToObject_UpdatesParameterMultipleTimes() { var binder = new CreatesCommandBindingViaCommandParameter(); var target = new CommandControl(); - var command = ReactiveCommand.Create(_ => { }); - var parameter = new BehaviorSubject(42); + var command = ReactiveCommand.Create(_ => { }); + var parameter = new BehaviorSubject("first"); using var binding = binder.BindCommandToObject(command, target, parameter); - await Assert.That(target.CommandParameter).IsEqualTo(42); - - parameter.OnNext(100); - await Assert.That(target.CommandParameter).IsEqualTo(100); - } - - [Test] - public async Task BindCommandToObject_RestoresOriginalValuesOnDispose() - { - var binder = new CreatesCommandBindingViaCommandParameter(); - var target = new CommandControl(); - var originalCommand = ReactiveCommand.Create(() => { }); - var originalParameter = "original"; - - target.Command = originalCommand; - target.CommandParameter = originalParameter; + await Assert.That(target.CommandParameter).IsEqualTo("first"); - var newCommand = ReactiveCommand.Create(() => { }); - using (var binding = binder.BindCommandToObject(newCommand, target, Observable.Return("new"))) - { - await Assert.That(target.Command).IsEqualTo(newCommand); - await Assert.That(target.CommandParameter).IsEqualTo("new"); - } + parameter.OnNext("second"); + await Assert.That(target.CommandParameter).IsEqualTo("second"); - await Assert.That(target.Command).IsEqualTo(originalCommand); - await Assert.That(target.CommandParameter).IsEqualTo(originalParameter); + parameter.OnNext("third"); + await Assert.That(target.CommandParameter).IsEqualTo("third"); } [Test] - public void BindCommandToObject_WithNullTarget_Throws() + public async Task BindCommandToObject_WithEventName_ReturnsEmptyDisposable() { var binder = new CreatesCommandBindingViaCommandParameter(); + var target = new CommandControl(); var command = ReactiveCommand.Create(() => { }); - Assert.Throws(() => - binder.BindCommandToObject(command, null, Observable.Return(null))); + var binding = binder.BindCommandToObject( + command, + target, + Observable.Return(null), + "SomeEvent"); + + await Assert.That(binding).IsEqualTo(Disposable.Empty); } [Test] @@ -129,34 +106,61 @@ public async Task BindCommandToObject_WithNullCommand_Succeeds() } [Test] - public async Task BindCommandToObject_WithEventName_ReturnsEmptyDisposable() + public void BindCommandToObject_WithNullTarget_Throws() { var binder = new CreatesCommandBindingViaCommandParameter(); - var target = new CommandControl(); var command = ReactiveCommand.Create(() => { }); - var binding = binder.BindCommandToObject(command, target, Observable.Return(null), "SomeEvent"); + Assert.Throws(() => + binder.BindCommandToObject(command, null, Observable.Return(null))); + } - await Assert.That(binding).IsEqualTo(Disposable.Empty); + [Test] + public async Task GetAffinityForObject_Generic_WithCommandAndCommandParameter_Returns5() + { + var binder = new CreatesCommandBindingViaCommandParameter(); + var affinity = binder.GetAffinityForObject(false); + await Assert.That(affinity).IsEqualTo(5); } [Test] - public async Task BindCommandToObject_UpdatesParameterMultipleTimes() + public async Task GetAffinityForObject_Generic_WithEventTarget_Returns0() { var binder = new CreatesCommandBindingViaCommandParameter(); - var target = new CommandControl(); - var command = ReactiveCommand.Create(_ => { }); - var parameter = new BehaviorSubject("first"); + var affinity = binder.GetAffinityForObject(true); + await Assert.That(affinity).IsEqualTo(0); + } - using var binding = binder.BindCommandToObject(command, target, parameter); + [Test] + public async Task GetAffinityForObject_WithCommandAndCommandParameter_Returns5() + { + var binder = new CreatesCommandBindingViaCommandParameter(); + var affinity = binder.GetAffinityForObject(false); + await Assert.That(affinity).IsEqualTo(5); + } - await Assert.That(target.CommandParameter).IsEqualTo("first"); + [Test] + public async Task GetAffinityForObject_WithEventTarget_Returns0() + { + var binder = new CreatesCommandBindingViaCommandParameter(); + var affinity = binder.GetAffinityForObject(true); + await Assert.That(affinity).IsEqualTo(0); + } - parameter.OnNext("second"); - await Assert.That(target.CommandParameter).IsEqualTo("second"); + [Test] + public async Task GetAffinityForObject_WithOnlyCommandProperty_Returns0() + { + var binder = new CreatesCommandBindingViaCommandParameter(); + var affinity = binder.GetAffinityForObject(false); + await Assert.That(affinity).IsEqualTo(0); + } - parameter.OnNext("third"); - await Assert.That(target.CommandParameter).IsEqualTo("third"); + [Test] + public async Task GetAffinityForObject_WithoutCommandProperty_Returns0() + { + var binder = new CreatesCommandBindingViaCommandParameter(); + var affinity = binder.GetAffinityForObject(false); + await Assert.That(affinity).IsEqualTo(0); } private class CommandControl diff --git a/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaEventTests.cs b/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaEventTests.cs index efb188055e..1b9c7d5739 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaEventTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaEventTests.cs @@ -3,130 +3,123 @@ // 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.Input; - namespace ReactiveUI.Tests.Bindings.CommandBindings; public class CreatesCommandBindingViaEventTests { [Test] - public async Task GetAffinityForObject_WithEventTarget_Returns5() + public async Task BindCommandToObject_AfterDispose_DoesNotExecuteCommand() { var binder = new CreatesCommandBindingViaEvent(); - var affinity = binder.GetAffinityForObject(typeof(ClickableControl), hasEventTarget: true); - await Assert.That(affinity).IsEqualTo(5); - } + var target = new ClickableControl(); + var wasCalled = false; + var command = ReactiveCommand.Create(() => wasCalled = true); - [Test] - public async Task GetAffinityForObject_WithClickEvent_Returns3() - { - var binder = new CreatesCommandBindingViaEvent(); - var affinity = binder.GetAffinityForObject(typeof(ClickableControl), hasEventTarget: false); - await Assert.That(affinity).IsEqualTo(3); - } + using (var binding = binder.BindCommandToObject(command, target, Observable.Return(null))) + { + // Binding is active + } - [Test] - public async Task GetAffinityForObject_WithMouseUpEvent_Returns3() - { - var binder = new CreatesCommandBindingViaEvent(); - var affinity = binder.GetAffinityForObject(typeof(MouseUpControl), hasEventTarget: false); - await Assert.That(affinity).IsEqualTo(3); + target.RaiseClick(); + await Assert.That(wasCalled).IsFalse(); } [Test] - public async Task GetAffinityForObject_WithNoEvents_Returns0() + public async Task BindCommandToObject_ChecksCanExecute() { var binder = new CreatesCommandBindingViaEvent(); - var affinity = binder.GetAffinityForObject(typeof(string), hasEventTarget: false); - await Assert.That(affinity).IsEqualTo(0); - } + var target = new ClickableControl(); + var executionCount = 0; + var canExecute = new BehaviorSubject(true); + var command = ReactiveCommand.Create(() => executionCount++, canExecute); - [Test] - public async Task GetAffinityForObject_Generic_WithEventTarget_Returns5() - { - var binder = new CreatesCommandBindingViaEvent(); - var affinity = binder.GetAffinityForObject(hasEventTarget: true); - await Assert.That(affinity).IsEqualTo(5); - } + using var binding = binder.BindCommandToObject(command, target, Observable.Return(null)); - [Test] - public async Task GetAffinityForObject_Generic_WithClickEvent_Returns3() - { - var binder = new CreatesCommandBindingViaEvent(); - var affinity = binder.GetAffinityForObject(hasEventTarget: false); - await Assert.That(affinity).IsEqualTo(3); + target.RaiseClick(); + await Assert.That(executionCount).IsEqualTo(1); + + canExecute.OnNext(false); + target.RaiseClick(); + await Assert.That(executionCount).IsEqualTo(1); // Should not execute when CanExecute is false } [Test] - public async Task BindCommandToObject_WithClickEvent_ExecutesCommand() + public async Task BindCommandToObject_MultipleClicks_ExecutesMultipleTimes() { var binder = new CreatesCommandBindingViaEvent(); var target = new ClickableControl(); - var wasCalled = false; - var command = ReactiveCommand.Create(() => wasCalled = true); + var executionCount = 0; + var command = ReactiveCommand.Create(() => executionCount++, outputScheduler: ImmediateScheduler.Instance); using var binding = binder.BindCommandToObject(command, target, Observable.Return(null)); target.RaiseClick(); - await Assert.That(wasCalled).IsTrue(); + target.RaiseClick(); + target.RaiseClick(); + + await Assert.That(executionCount).IsEqualTo(3); } [Test] - public async Task BindCommandToObject_WithParameter_PassesParameterToCommand() + public async Task BindCommandToObject_UpdatesParameter_UsesLatestParameter() { var binder = new CreatesCommandBindingViaEvent(); var target = new ClickableControl(); object? receivedParameter = null; var command = ReactiveCommand.Create(param => receivedParameter = param); - var parameter = new BehaviorSubject("test"); + var parameter = new BehaviorSubject("first"); using var binding = binder.BindCommandToObject(command, target, parameter); + parameter.OnNext("second"); target.RaiseClick(); - await Assert.That(receivedParameter).IsEqualTo("test"); + await Assert.That(receivedParameter).IsEqualTo("second"); } [Test] - public async Task BindCommandToObject_UpdatesParameter_UsesLatestParameter() + public async Task BindCommandToObject_WithClickEvent_ExecutesCommand() { var binder = new CreatesCommandBindingViaEvent(); var target = new ClickableControl(); - object? receivedParameter = null; - var command = ReactiveCommand.Create(param => receivedParameter = param); - var parameter = new BehaviorSubject("first"); + var wasCalled = false; + var command = ReactiveCommand.Create(() => wasCalled = true); - using var binding = binder.BindCommandToObject(command, target, parameter); + using var binding = binder.BindCommandToObject(command, target, Observable.Return(null)); - parameter.OnNext("second"); target.RaiseClick(); - await Assert.That(receivedParameter).IsEqualTo("second"); + await Assert.That(wasCalled).IsTrue(); } [Test] - public async Task BindCommandToObject_AfterDispose_DoesNotExecuteCommand() + public async Task BindCommandToObject_WithExplicitEvent_BindsToSpecifiedEvent() { var binder = new CreatesCommandBindingViaEvent(); var target = new ClickableControl(); var wasCalled = false; var command = ReactiveCommand.Create(() => wasCalled = true); - using (var binding = binder.BindCommandToObject(command, target, Observable.Return(null))) - { - // Binding is active - } + using var binding = binder.BindCommandToObject( + command, + target, + Observable.Return(null), + "Click"); target.RaiseClick(); - await Assert.That(wasCalled).IsFalse(); + await Assert.That(wasCalled).IsTrue(); } [Test] - public void BindCommandToObject_WithNullTarget_Throws() + public async Task BindCommandToObject_WithMouseUpEvent_ExecutesCommand() { var binder = new CreatesCommandBindingViaEvent(); - var command = ReactiveCommand.Create(() => { }); + var target = new MouseUpControl(); + var wasCalled = false; + var command = ReactiveCommand.Create(() => wasCalled = true); - Assert.Throws(() => - binder.BindCommandToObject(command, null, Observable.Return(null))); + using var binding = binder.BindCommandToObject(command, target, Observable.Return(null)); + + target.RaiseMouseUp(); + await Assert.That(wasCalled).IsTrue(); } [Test] @@ -141,71 +134,76 @@ public void BindCommandToObject_WithNoEvents_Throws() } [Test] - public async Task BindCommandToObject_WithExplicitEvent_BindsToSpecifiedEvent() + public void BindCommandToObject_WithNullTarget_Throws() { var binder = new CreatesCommandBindingViaEvent(); - var target = new ClickableControl(); - var wasCalled = false; - var command = ReactiveCommand.Create(() => wasCalled = true); - - using var binding = binder.BindCommandToObject( - command, - target, - Observable.Return(null), - "Click"); + var command = ReactiveCommand.Create(() => { }); - target.RaiseClick(); - await Assert.That(wasCalled).IsTrue(); + Assert.Throws(() => + binder.BindCommandToObject(command, null, Observable.Return(null))); } [Test] - public async Task BindCommandToObject_ChecksCanExecute() + public async Task BindCommandToObject_WithParameter_PassesParameterToCommand() { var binder = new CreatesCommandBindingViaEvent(); var target = new ClickableControl(); - var executionCount = 0; - var canExecute = new BehaviorSubject(true); - var command = ReactiveCommand.Create(() => executionCount++, canExecute); - - using var binding = binder.BindCommandToObject(command, target, Observable.Return(null)); + object? receivedParameter = null; + var command = ReactiveCommand.Create(param => receivedParameter = param); + var parameter = new BehaviorSubject("test"); - target.RaiseClick(); - await Assert.That(executionCount).IsEqualTo(1); + using var binding = binder.BindCommandToObject(command, target, parameter); - canExecute.OnNext(false); target.RaiseClick(); - await Assert.That(executionCount).IsEqualTo(1); // Should not execute when CanExecute is false + await Assert.That(receivedParameter).IsEqualTo("test"); } [Test] - public async Task BindCommandToObject_MultipleClicks_ExecutesMultipleTimes() + public async Task GetAffinityForObject_Generic_WithClickEvent_Returns3() { var binder = new CreatesCommandBindingViaEvent(); - var target = new ClickableControl(); - var executionCount = 0; - var command = ReactiveCommand.Create(() => executionCount++); - - using var binding = binder.BindCommandToObject(command, target, Observable.Return(null)); + var affinity = binder.GetAffinityForObject(false); + await Assert.That(affinity).IsEqualTo(3); + } - target.RaiseClick(); - target.RaiseClick(); - target.RaiseClick(); + [Test] + public async Task GetAffinityForObject_Generic_WithEventTarget_Returns5() + { + var binder = new CreatesCommandBindingViaEvent(); + var affinity = binder.GetAffinityForObject(true); + await Assert.That(affinity).IsEqualTo(5); + } - await Assert.That(executionCount).IsEqualTo(3); + [Test] + public async Task GetAffinityForObject_WithClickEvent_Returns3() + { + var binder = new CreatesCommandBindingViaEvent(); + var affinity = binder.GetAffinityForObject(false); + await Assert.That(affinity).IsEqualTo(3); } [Test] - public async Task BindCommandToObject_WithMouseUpEvent_ExecutesCommand() + public async Task GetAffinityForObject_WithEventTarget_Returns5() { var binder = new CreatesCommandBindingViaEvent(); - var target = new MouseUpControl(); - var wasCalled = false; - var command = ReactiveCommand.Create(() => wasCalled = true); + var affinity = binder.GetAffinityForObject(true); + await Assert.That(affinity).IsEqualTo(5); + } - using var binding = binder.BindCommandToObject(command, target, Observable.Return(null)); + [Test] + public async Task GetAffinityForObject_WithMouseUpEvent_Returns3() + { + var binder = new CreatesCommandBindingViaEvent(); + var affinity = binder.GetAffinityForObject(false); + await Assert.That(affinity).IsEqualTo(3); + } - target.RaiseMouseUp(); - await Assert.That(wasCalled).IsTrue(); + [Test] + public async Task GetAffinityForObject_WithNoEvents_Returns0() + { + var binder = new CreatesCommandBindingViaEvent(); + var affinity = binder.GetAffinityForObject(false); + await Assert.That(affinity).IsEqualTo(0); } private class ClickableControl diff --git a/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterAffinityTests.cs b/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterAffinityTests.cs new file mode 100644 index 0000000000..b0b83505c3 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterAffinityTests.cs @@ -0,0 +1,141 @@ +// Copyright (c) 2025 .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 verifying converter affinity values are correctly set. +/// Uses TUnit's MethodDataSource for theory-style testing with compile-time safety. +/// +public class ConverterAffinityTests +{ + /// + /// Data source for standard converters (affinity = 2). + /// + /// A sequence of converter test data with expected affinity values. + public static IEnumerable> GetStandardConverters() + { + // String identity converter + yield return () => (new StringConverter(), 2); + + // Numeric → String converters + yield return () => (new ByteToStringTypeConverter(), 2); + yield return () => (new NullableByteToStringTypeConverter(), 2); + yield return () => (new ShortToStringTypeConverter(), 2); + yield return () => (new NullableShortToStringTypeConverter(), 2); + yield return () => (new IntegerToStringTypeConverter(), 2); + yield return () => (new NullableIntegerToStringTypeConverter(), 2); + yield return () => (new LongToStringTypeConverter(), 2); + yield return () => (new NullableLongToStringTypeConverter(), 2); + yield return () => (new SingleToStringTypeConverter(), 2); + yield return () => (new NullableSingleToStringTypeConverter(), 2); + yield return () => (new DoubleToStringTypeConverter(), 2); + yield return () => (new NullableDoubleToStringTypeConverter(), 2); + yield return () => (new DecimalToStringTypeConverter(), 2); + yield return () => (new NullableDecimalToStringTypeConverter(), 2); + + // String → Numeric converters + yield return () => (new StringToByteTypeConverter(), 2); + yield return () => (new StringToNullableByteTypeConverter(), 2); + yield return () => (new StringToShortTypeConverter(), 2); + yield return () => (new StringToNullableShortTypeConverter(), 2); + yield return () => (new StringToIntegerTypeConverter(), 2); + yield return () => (new StringToNullableIntegerTypeConverter(), 2); + yield return () => (new StringToLongTypeConverter(), 2); + yield return () => (new StringToNullableLongTypeConverter(), 2); + yield return () => (new StringToSingleTypeConverter(), 2); + yield return () => (new StringToNullableSingleTypeConverter(), 2); + yield return () => (new StringToDoubleTypeConverter(), 2); + yield return () => (new StringToNullableDoubleTypeConverter(), 2); + yield return () => (new StringToDecimalTypeConverter(), 2); + yield return () => (new StringToNullableDecimalTypeConverter(), 2); + + // Boolean ↔ String converters + yield return () => (new BooleanToStringTypeConverter(), 2); + yield return () => (new NullableBooleanToStringTypeConverter(), 2); + yield return () => (new StringToBooleanTypeConverter(), 2); + yield return () => (new StringToNullableBooleanTypeConverter(), 2); + + // Guid ↔ String converters + yield return () => (new GuidToStringTypeConverter(), 2); + yield return () => (new NullableGuidToStringTypeConverter(), 2); + yield return () => (new StringToGuidTypeConverter(), 2); + yield return () => (new StringToNullableGuidTypeConverter(), 2); + + // DateTime ↔ String converters + yield return () => (new DateTimeToStringTypeConverter(), 2); + yield return () => (new NullableDateTimeToStringTypeConverter(), 2); + yield return () => (new StringToDateTimeTypeConverter(), 2); + yield return () => (new StringToNullableDateTimeTypeConverter(), 2); + + // DateTimeOffset ↔ String converters + yield return () => (new DateTimeOffsetToStringTypeConverter(), 2); + yield return () => (new NullableDateTimeOffsetToStringTypeConverter(), 2); + yield return () => (new StringToDateTimeOffsetTypeConverter(), 2); + yield return () => (new StringToNullableDateTimeOffsetTypeConverter(), 2); + + // TimeSpan ↔ String converters + yield return () => (new TimeSpanToStringTypeConverter(), 2); + yield return () => (new NullableTimeSpanToStringTypeConverter(), 2); + yield return () => (new StringToTimeSpanTypeConverter(), 2); + yield return () => (new StringToNullableTimeSpanTypeConverter(), 2); + +#if NET6_0_OR_GREATER + + // DateOnly ↔ String converters (.NET 6+) + yield return () => (new DateOnlyToStringTypeConverter(), 2); + yield return () => (new NullableDateOnlyToStringTypeConverter(), 2); + yield return () => (new StringToDateOnlyTypeConverter(), 2); + yield return () => (new StringToNullableDateOnlyTypeConverter(), 2); + + // TimeOnly ↔ String converters (.NET 6+) + yield return () => (new TimeOnlyToStringTypeConverter(), 2); + yield return () => (new NullableTimeOnlyToStringTypeConverter(), 2); + yield return () => (new StringToTimeOnlyTypeConverter(), 2); + yield return () => (new StringToNullableTimeOnlyTypeConverter(), 2); +#endif + + // Uri ↔ String converters + yield return () => (new UriToStringTypeConverter(), 2); + yield return () => (new StringToUriTypeConverter(), 2); + } + + /// + /// Verifies that the EqualityTypeConverter has affinity 1 (last resort). + /// + /// A task representing the asynchronous operation. + [Test] + public async Task EqualityConverter_ShouldHaveAffinity1() + { + // Arrange + var converter = new EqualityTypeConverter(); + + // Act + var affinity = converter.GetAffinityForObjects(); + + // Assert + await Assert.That(affinity).IsEqualTo(1); + } + + /// + /// Verifies that all standard converters have affinity 2. + /// Standard converters are the core ReactiveUI converters (numeric, string, datetime, etc.). + /// + /// The converter to test. + /// The expected affinity value. + /// A task representing the asynchronous operation. + [Test] + [MethodDataSource(nameof(GetStandardConverters))] + public async Task StandardConverters_ShouldHaveAffinity2(IBindingTypeConverter converter, int expectedAffinity) + { + ArgumentNullException.ThrowIfNull(converter); + + // Act + var actualAffinity = converter.GetAffinityForObjects(); + + // Assert + await Assert.That(actualAffinity).IsEqualTo(expectedAffinity); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterRegistryTests.cs b/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterRegistryTests.cs new file mode 100644 index 0000000000..a0d3a92df7 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterRegistryTests.cs @@ -0,0 +1,306 @@ +// Copyright (c) 2025 .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 the lock-free converter registries. +/// Verifies thread-safety, affinity-based selection, and snapshot pattern behavior. +/// +public class ConverterRegistryTests +{ + /// + /// Verifies that the registry supports concurrent reads during registration. + /// This tests the lock-free snapshot pattern. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task ConcurrentReads_DuringRegistration_ShouldBeThreadSafe() + { + // Arrange + var registry = new BindingTypeConverterRegistry(); + var converter1 = new TestConverter(5); + var converter2 = new TestConverter(3); + registry.Register(converter1); + + var readTasks = new List>(); + var writeTasks = new List(); + + // Act - Start concurrent reads and writes + for (var i = 0; i < 100; i++) + { + // Concurrent reads + readTasks.Add(Task.Run(() => registry.TryGetConverter(typeof(int), typeof(string)))); + + // Concurrent write + if (i == 50) + { + writeTasks.Add(Task.Run(() => registry.Register(converter2))); + } + } + + await Task.WhenAll(readTasks.Concat(writeTasks)); + + // Assert - All reads should have completed successfully + foreach (var task in readTasks) + { + var result = await task; + await Assert.That(result).IsNotNull(); // Should always get converter1 + } + + // Verify both converters are registered + var finalCheck1 = registry.TryGetConverter(typeof(int), typeof(string)); + var finalCheck2 = registry.TryGetConverter(typeof(double), typeof(bool)); + + await Assert.That(finalCheck1).IsEqualTo(converter1); + await Assert.That(finalCheck2).IsEqualTo(converter2); + } + + /// + /// Verifies that converters with negative affinity are ignored. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task ConverterWithNegativeAffinity_ShouldBeIgnored() + { + // Arrange + var registry = new BindingTypeConverterRegistry(); + var negativeAffinity = new TestConverter(-5); + var validAffinity = new TestConverter(2); + + // Act + registry.Register(negativeAffinity); + registry.Register(validAffinity); + + var selected = registry.TryGetConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(selected).IsEqualTo(validAffinity); + } + + /// + /// Verifies that converters with affinity 0 are ignored. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task ConverterWithZeroAffinity_ShouldBeIgnored() + { + // Arrange + var registry = new BindingTypeConverterRegistry(); + var zeroAffinity = new TestConverter(0); + var validAffinity = new TestConverter(2); + + // Act + registry.Register(zeroAffinity); + registry.Register(validAffinity); + + var selected = registry.TryGetConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(selected).IsEqualTo(validAffinity); + } + + /// + /// Verifies that an empty registry returns null. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task EmptyRegistry_ShouldReturnNull() + { + // Arrange + var registry = new BindingTypeConverterRegistry(); + + // Act + var result = registry.TryGetConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(result).IsNull(); + } + + /// + /// Verifies that fallback converter registry works correctly. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task FallbackRegistry_ShouldSelectHighestAffinity() + { + // Arrange + var registry = new BindingFallbackConverterRegistry(); + var lowAffinity = new TestFallbackConverter(2); + var highAffinity = new TestFallbackConverter(10); + + // Act + registry.Register(lowAffinity); + registry.Register(highAffinity); + + var selected = registry.TryGetConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(selected).IsEqualTo(highAffinity); + } + + /// + /// Verifies that GetAllConverters returns all registered converters. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task GetAllConverters_ShouldReturnAllRegistered() + { + // Arrange + var registry = new BindingTypeConverterRegistry(); + var converter1 = new TestConverter(5); + var converter2 = new TestConverter(3); + var converter3 = new TestConverter(2); + + // Act + registry.Register(converter1); + registry.Register(converter2); + registry.Register(converter3); + + var allConverters = registry.GetAllConverters().ToList(); + + // Assert + await Assert.That(allConverters.Count).IsEqualTo(3); + await Assert.That(allConverters).Contains(converter1); + await Assert.That(allConverters).Contains(converter2); + await Assert.That(allConverters).Contains(converter3); + } + + /// + /// Verifies that when multiple converters are registered for the same type pair, + /// the one with the highest affinity is selected. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task MultipleConverters_ShouldSelectHighestAffinity() + { + // Arrange + var registry = new BindingTypeConverterRegistry(); + var lowAffinity = new TestConverter(2); + var mediumAffinity = new TestConverter(5); + var highAffinity = new TestConverter(10); + + // Act - register in random order + registry.Register(mediumAffinity); + registry.Register(lowAffinity); + registry.Register(highAffinity); + + var selected = registry.TryGetConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(selected).IsEqualTo(highAffinity); + } + + /// + /// Verifies that requesting a non-existent type pair returns null. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task NonExistentTypePair_ShouldReturnNull() + { + // Arrange + var registry = new BindingTypeConverterRegistry(); + var converter = new TestConverter(5); + registry.Register(converter); + + // Act + var result = registry.TryGetConverter(typeof(double), typeof(bool)); + + // Assert + await Assert.That(result).IsNull(); + } + + /// + /// Verifies that a registered converter can be retrieved. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task Register_AndRetrieve_ShouldReturnConverter() + { + // Arrange + var registry = new BindingTypeConverterRegistry(); + var converter = new TestConverter(5); + + // Act + registry.Register(converter); + var retrieved = registry.TryGetConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(retrieved).IsNotNull(); + await Assert.That(retrieved).IsEqualTo(converter); + } + + /// + /// Verifies that set-method converter registry works correctly. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task SetMethodRegistry_ShouldSelectHighestAffinity() + { + // Arrange + var registry = new SetMethodBindingConverterRegistry(); + var lowAffinity = new TestSetMethodConverter(2); + var highAffinity = new TestSetMethodConverter(8); + + // Act + registry.Register(lowAffinity); + registry.Register(highAffinity); + + var selected = registry.TryGetConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(selected).IsEqualTo(highAffinity); + } + + private sealed class TestConverter : BindingTypeConverter + { + private readonly int _affinity; + + public TestConverter(int affinity) => _affinity = affinity; + + public override int GetAffinityForObjects() => _affinity; + + public override bool TryConvert(TFrom? from, object? conversionHint, [NotNullWhen(true)] out TTo? result) + { + result = default; + return false; + } + } + + private sealed class TestFallbackConverter : IBindingFallbackConverter + { + private readonly int _baseAffinity; + + public TestFallbackConverter(int baseAffinity) => _baseAffinity = baseAffinity; + + public int GetAffinityForObjects( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type toType) => _baseAffinity; + + 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; + } + } + + private sealed class TestSetMethodConverter : ISetMethodBindingConverter + { + private readonly int _baseAffinity; + + public TestSetMethodConverter(int baseAffinity) => _baseAffinity = baseAffinity; + + public int GetAffinityForObjects(Type? fromType, Type? toType) => _baseAffinity; + + public object? PerformSet(object? toTarget, object? newValue, object?[]? arguments) => newValue; + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterServiceIntegrationTests.cs b/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterServiceIntegrationTests.cs new file mode 100644 index 0000000000..fe5adf117a --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/Converters/ConverterServiceIntegrationTests.cs @@ -0,0 +1,279 @@ +// Copyright (c) 2025 .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; + +/// +/// Integration tests for the ConverterService. +/// Verifies end-to-end converter resolution with typed and fallback converters. +/// +public class ConverterServiceIntegrationTests +{ + /// + /// Verifies that all three registries are accessible. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task ConverterService_ShouldExposeAllRegistries() + { + // Arrange + var service = new ConverterService(); + + // Assert + await Assert.That(service.TypedConverters).IsNotNull(); + await Assert.That(service.FallbackConverters).IsNotNull(); + await Assert.That(service.SetMethodConverters).IsNotNull(); + } + + /// + /// Verifies that custom converters with high affinity can override defaults. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task CustomHighAffinityConverter_ShouldOverrideDefault() + { + // Arrange + var service = new ConverterService(); + var defaultConverter = new TestTypedConverter(2); + var customConverter = new TestTypedConverter(100); + + service.TypedConverters.Register(defaultConverter); + service.TypedConverters.Register(customConverter); + + // Act + var result = service.ResolveConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(result).IsEqualTo(customConverter); + } + + /// + /// Verifies that fallback converters are used when no typed converter matches. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task FallbackConverter_ShouldBeUsedWhenNoTypedMatch() + { + // Arrange + var service = new ConverterService(); + var typedConverter = new TestTypedConverter(5); + var fallbackConverter = new TestFallbackConverter(3); + + service.TypedConverters.Register(typedConverter); + service.FallbackConverters.Register(fallbackConverter); + + // Act - Request different type pair (not int->string) + var result = service.ResolveConverter(typeof(double), typeof(bool)); + + // Assert - Fallback converter should be used + await Assert.That(result).IsEqualTo(fallbackConverter); + } + + /// + /// Verifies that the highest affinity fallback converter is selected. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task MultipleFallbackConverters_ShouldSelectHighestAffinity() + { + // Arrange + var service = new ConverterService(); + var lowAffinity = new TestFallbackConverter(2); + var mediumAffinity = new TestFallbackConverter(5); + var highAffinity = new TestFallbackConverter(10); + + service.FallbackConverters.Register(mediumAffinity); + service.FallbackConverters.Register(lowAffinity); + service.FallbackConverters.Register(highAffinity); + + // Act + var result = service.ResolveConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(result).IsEqualTo(highAffinity); + } + + /// + /// Verifies that null is returned when no converter matches. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task NoConverter_ShouldReturnNull() + { + // Arrange + var service = new ConverterService(); + var converter = new TestTypedConverter(5); + service.TypedConverters.Register(converter); + + // Act + var result = service.ResolveConverter(typeof(double), typeof(bool)); + + // Assert + await Assert.That(result).IsNull(); + } + + /// + /// Verifies end-to-end integration with real converters. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task RealConverters_ShouldResolveCorrectly() + { + // Arrange + var service = new ConverterService(); + var intToString = new IntegerToStringTypeConverter(); + var stringToInt = new StringToIntegerTypeConverter(); + var equality = new EqualityTypeConverter(); + + service.TypedConverters.Register(intToString); + service.TypedConverters.Register(stringToInt); + service.TypedConverters.Register(equality); + + // Act + var result1 = service.ResolveConverter(typeof(int), typeof(string)); + var result2 = service.ResolveConverter(typeof(string), typeof(int)); + + // Assert + await Assert.That(result1).IsEqualTo(intToString); + await Assert.That(result2).IsEqualTo(stringToInt); + } + + /// + /// Verifies that RxConverters.Current works after being set. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task RxConverters_CurrentShouldBeAccessible() + { + // Arrange + var service = new ConverterService(); + var converter = new TestTypedConverter(5); + service.TypedConverters.Register(converter); + + // Act + RxConverters.SetService(service); + var result = RxConverters.Current.ResolveConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(result).IsEqualTo(converter); + + // Cleanup - reset to default + RxConverters.SetService(new ConverterService()); + } + + /// + /// Verifies that set-method converters can be registered and retrieved. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task SetMethodConverter_ShouldBeRetrievable() + { + // Arrange + var service = new ConverterService(); + var setMethodConverter = new TestSetMethodConverter(8); + + service.SetMethodConverters.Register(setMethodConverter); + + // Act + var result = service.SetMethodConverters.TryGetConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(result).IsEqualTo(setMethodConverter); + } + + /// + /// Verifies that typed converters are selected before fallback converters. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task TypedConverter_ShouldBePreferredOverFallback() + { + // Arrange + var service = new ConverterService(); + var typedConverter = new TestTypedConverter(2); + var fallbackConverter = new TestFallbackConverter(10); // Higher affinity but should lose to typed + + service.TypedConverters.Register(typedConverter); + service.FallbackConverters.Register(fallbackConverter); + + // Act + var result = service.ResolveConverter(typeof(int), typeof(string)); + + // Assert - Typed converter should win even with lower affinity + await Assert.That(result).IsEqualTo(typedConverter); + } + + /// + /// Verifies that converters with affinity 0 are ignored in resolution. + /// + /// A task representing the asynchronous operation. + [Test] + public async Task ZeroAffinityConverter_ShouldBeIgnoredInResolution() + { + // Arrange + var service = new ConverterService(); + var zeroAffinity = new TestTypedConverter(0); + var validAffinity = new TestTypedConverter(2); + + service.TypedConverters.Register(zeroAffinity); + service.TypedConverters.Register(validAffinity); + + // Act + var result = service.ResolveConverter(typeof(int), typeof(string)); + + // Assert + await Assert.That(result).IsEqualTo(validAffinity); + } + + private sealed class TestFallbackConverter : IBindingFallbackConverter + { + private readonly int _baseAffinity; + + public TestFallbackConverter(int baseAffinity) => _baseAffinity = baseAffinity; + + public int GetAffinityForObjects( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type fromType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type toType) => _baseAffinity; + + 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; + } + } + + private sealed class TestSetMethodConverter : ISetMethodBindingConverter + { + private readonly int _baseAffinity; + + public TestSetMethodConverter(int baseAffinity) => _baseAffinity = baseAffinity; + + public int GetAffinityForObjects(Type? fromType, Type? toType) => _baseAffinity; + + public object? PerformSet(object? toTarget, object? newValue, object?[]? arguments) => newValue; + } + + private sealed class TestTypedConverter : BindingTypeConverter + { + private readonly int _affinity; + + public TestTypedConverter(int affinity) => _affinity = affinity; + + public override int GetAffinityForObjects() => _affinity; + + public override bool TryConvert(TFrom? from, object? conversionHint, [NotNullWhen(true)] out TTo? result) + { + result = default; + return false; + } + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/Converters/PlatformConverterAffinityTests.cs b/src/tests/ReactiveUI.Tests/Bindings/Converters/PlatformConverterAffinityTests.cs new file mode 100644 index 0000000000..33ac0815ba --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/Converters/PlatformConverterAffinityTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2025 .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 HAS_MAUI || HAS_WPF || HAS_WINUI || HAS_UNO +namespace ReactiveUI.Tests.Bindings.Converters; + +/// +/// Tests for verifying platform-specific converter affinity values. +/// Uses TUnit's MethodDataSource for theory-style testing with compile-time safety. +/// +public class PlatformConverterAffinityTests +{ + /// + /// Verifies that platform-specific converters have the correct affinity values. + /// Platform converters should have affinity 2 (same as standard converters). + /// + /// A task representing the asynchronous operation. + [Test] + [MethodDataSource(nameof(GetPlatformConverters))] + public async Task PlatformConverters_ShouldHaveCorrectAffinity(IBindingTypeConverter converter, int expectedAffinity) + { + // Act + var actualAffinity = converter.GetAffinityForObjects(); + + // Assert + await Assert.That(actualAffinity).IsEqualTo(expectedAffinity); + } + + /// + /// Data source for platform-specific converters. + /// + public static IEnumerable<(IBindingTypeConverter converter, int expectedAffinity)> GetPlatformConverters() + { +#if HAS_WPF || HAS_WINUI || HAS_UNO + // WPF/WinUI/UNO visibility converters (affinity = 2, same as standard converters) + yield return (new ReactiveUI.BooleanToVisibilityTypeConverter(), 2); + yield return (new ReactiveUI.VisibilityToBooleanTypeConverter(), 2); +#endif + +#if IS_MAUI + // MAUI visibility converters (affinity = 2, same as standard converters) + yield return (new ReactiveUI.Maui.BooleanToVisibilityTypeConverter(), 2); + yield return (new ReactiveUI.Maui.VisibilityToBooleanTypeConverter(), 2); +#endif + } +} +#endif diff --git a/src/tests/ReactiveUI.Tests/Bindings/PropertyBindings/PropertyBindingMixinsTests.cs b/src/tests/ReactiveUI.Tests/Bindings/PropertyBindings/PropertyBindingMixinsTests.cs index 3b0dbb579d..bba146b4b4 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/PropertyBindings/PropertyBindingMixinsTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/PropertyBindings/PropertyBindingMixinsTests.cs @@ -7,6 +7,21 @@ namespace ReactiveUI.Tests.Bindings.PropertyBindings; public class PropertyBindingMixinsTests { + [Test] + public async Task Bind_AfterDispose_StopsUpdating() + { + var viewModel = new TestViewModel { Name = "Initial" }; + var view = new TestView { ViewModel = viewModel }; + + using (var binding = view.Bind(viewModel, vm => vm.Name, v => v.NameText)) + { + await Assert.That(view.NameText).IsEqualTo("Initial"); + } + + viewModel.Name = "Changed"; + await Assert.That(view.NameText).IsEqualTo("Initial"); + } + [Test] public async Task Bind_WithBasicProperties_UpdatesBothDirections() { @@ -27,21 +42,6 @@ public async Task Bind_WithBasicProperties_UpdatesBothDirections() await Assert.That(view.NameText).IsEqualTo("Changed"); } - [Test] - public async Task Bind_AfterDispose_StopsUpdating() - { - var viewModel = new TestViewModel { Name = "Initial" }; - var view = new TestView { ViewModel = viewModel }; - - using (var binding = view.Bind(viewModel, vm => vm.Name, v => v.NameText)) - { - await Assert.That(view.NameText).IsEqualTo("Initial"); - } - - viewModel.Name = "Changed"; - await Assert.That(view.NameText).IsEqualTo("Initial"); - } - [Test] public async Task Bind_WithConverters_ConvertsValues() { @@ -100,68 +100,36 @@ public async Task Bind_WithSignalViewUpdate_UpdatesOnSignal() } [Test] - public async Task OneWayBind_UpdatesViewOnly() - { - var viewModel = new TestViewModel { Name = "Initial" }; - var view = new TestView { ViewModel = viewModel }; - - using var binding = view.OneWayBind(viewModel, vm => vm.Name, v => v.NameText); - - // VM to View - await Assert.That(view.NameText).IsEqualTo("Initial"); - - viewModel.Name = "Updated"; - await Assert.That(view.NameText).IsEqualTo("Updated"); - - // View to VM should not work - view.NameText = "ViewChange"; - await Assert.That(viewModel.Name).IsEqualTo("Updated"); - } - - [Test] - public async Task OneWayBind_WithSelector_TransformsValue() + public async Task Bind_WithTypeConverter_ConvertsTypes() { var viewModel = new TestViewModel { Count = 42 }; var view = new TestView { ViewModel = viewModel }; - using var binding = view.OneWayBind( + using var binding = view.Bind( viewModel, vm => vm.Count, - v => v.NameText, - count => $"The count is: {count}"); + v => v.NameText); - await Assert.That(view.NameText).IsEqualTo("The count is: 42"); + // Should convert int to string automatically + await Assert.That(view.NameText).IsEqualTo("42"); - viewModel.Count = 100; - await Assert.That(view.NameText).IsEqualTo("The count is: 100"); + view.NameText = "100"; + await Assert.That(viewModel.Count).IsEqualTo(100); } [Test] - public async Task OneWayBind_AfterDispose_StopsUpdating() + public async Task BindTo_AfterDispose_StopsUpdating() { - var viewModel = new TestViewModel { Name = "Initial" }; - var view = new TestView { ViewModel = viewModel }; + var target = new TestViewModel(); + var source = new BehaviorSubject("Initial"); - using (var binding = view.OneWayBind(viewModel, vm => vm.Name, v => v.NameText)) + using (var binding = source.BindTo(target, t => t.Name)) { - await Assert.That(view.NameText).IsEqualTo("Initial"); + await Assert.That(target.Name).IsEqualTo("Initial"); } - viewModel.Name = "Changed"; - await Assert.That(view.NameText).IsEqualTo("Initial"); - } - - [Test] - public async Task OneWayBind_WithNullViewModel_DoesNotThrow() - { - var view = new TestView(); - - using var binding = view.OneWayBind( - null, - vm => vm.Name!, - v => v.NameText!); - - await Assert.That(view.NameText).IsNull(); + source.OnNext("Updated"); + await Assert.That(target.Name).IsEqualTo("Initial"); } [Test] @@ -181,21 +149,6 @@ public async Task BindTo_UpdatesTargetProperty() await Assert.That(target.Name).IsEqualTo("Final"); } - [Test] - public async Task BindTo_AfterDispose_StopsUpdating() - { - var target = new TestViewModel(); - var source = new BehaviorSubject("Initial"); - - using (var binding = source.BindTo(target, t => t.Name)) - { - await Assert.That(target.Name).IsEqualTo("Initial"); - } - - source.OnNext("Updated"); - await Assert.That(target.Name).IsEqualTo("Initial"); - } - [Test] public async Task BindTo_WithConverter_ConvertsValue() { @@ -214,21 +167,68 @@ public async Task BindTo_WithConverter_ConvertsValue() } [Test] - public async Task Bind_WithTypeConverter_ConvertsTypes() + public async Task OneWayBind_AfterDispose_StopsUpdating() + { + var viewModel = new TestViewModel { Name = "Initial" }; + var view = new TestView { ViewModel = viewModel }; + + using (var binding = view.OneWayBind(viewModel, vm => vm.Name, v => v.NameText)) + { + await Assert.That(view.NameText).IsEqualTo("Initial"); + } + + viewModel.Name = "Changed"; + await Assert.That(view.NameText).IsEqualTo("Initial"); + } + + [Test] + public async Task OneWayBind_UpdatesViewOnly() + { + var viewModel = new TestViewModel { Name = "Initial" }; + var view = new TestView { ViewModel = viewModel }; + + using var binding = view.OneWayBind(viewModel, vm => vm.Name, v => v.NameText); + + // VM to View + await Assert.That(view.NameText).IsEqualTo("Initial"); + + viewModel.Name = "Updated"; + await Assert.That(view.NameText).IsEqualTo("Updated"); + + // View to VM should not work + view.NameText = "ViewChange"; + await Assert.That(viewModel.Name).IsEqualTo("Updated"); + } + + [Test] + public async Task OneWayBind_WithNullViewModel_DoesNotThrow() + { + var view = new TestView(); + + using var binding = view.OneWayBind( + null, + vm => vm.Name!, + v => v.NameText!); + + await Assert.That(view.NameText).IsNull(); + } + + [Test] + public async Task OneWayBind_WithSelector_TransformsValue() { var viewModel = new TestViewModel { Count = 42 }; var view = new TestView { ViewModel = viewModel }; - using var binding = view.Bind( + using var binding = view.OneWayBind( viewModel, vm => vm.Count, - v => v.NameText); + v => v.NameText, + count => $"The count is: {count}"); - // Should convert int to string automatically - await Assert.That(view.NameText).IsEqualTo("42"); + await Assert.That(view.NameText).IsEqualTo("The count is: 42"); - view.NameText = "100"; - await Assert.That(viewModel.Count).IsEqualTo(100); + viewModel.Count = 100; + await Assert.That(view.NameText).IsEqualTo("The count is: 100"); } [Test] @@ -248,33 +248,40 @@ public async Task OneWayBind_WithTypeConverter_ConvertsTypes() await Assert.That(view.NameText).IsEqualTo("100"); } - private class TestViewModel : ReactiveObject + private class FuncBindingTypeConverter : IBindingTypeConverter { - private string? _name; - private int _count; + private readonly Func _converter; - public string? Name - { - get => _name; - set => this.RaiseAndSetIfChanged(ref _name, value); - } + public FuncBindingTypeConverter(Func converter) => _converter = converter; - public int Count + public Type FromType => typeof(TFrom); + + public Type ToType => typeof(TTo); + + public int GetAffinityForObjects() => 100; + + public bool TryConvertTyped(object? from, object? conversionHint, out object? result) { - get => _count; - set => this.RaiseAndSetIfChanged(ref _count, value); + if (from is TFrom typedFrom) + { + result = _converter(typedFrom)!; + return true; + } + + result = default(TTo); + return false; } } private class TestView : ReactiveObject, IViewFor { - private TestViewModel? _viewModel; private string? _nameText; + private TestViewModel? _viewModel; - object? IViewFor.ViewModel + public string? NameText { - get => ViewModel; - set => ViewModel = (TestViewModel?)value; + get => _nameText; + set => this.RaiseAndSetIfChanged(ref _nameText, value); } public TestViewModel? ViewModel @@ -283,35 +290,28 @@ public TestViewModel? ViewModel set => this.RaiseAndSetIfChanged(ref _viewModel, value); } - public string? NameText + object? IViewFor.ViewModel { - get => _nameText; - set => this.RaiseAndSetIfChanged(ref _nameText, value); + get => ViewModel; + set => ViewModel = (TestViewModel?)value; } } - private class FuncBindingTypeConverter : IBindingTypeConverter + private class TestViewModel : ReactiveObject { - private readonly Func _converter; + private int _count; + private string? _name; - public FuncBindingTypeConverter(Func converter) + public int Count { - _converter = converter; + get => _count; + set => this.RaiseAndSetIfChanged(ref _count, value); } - public int GetAffinityForObjects(Type fromType, Type toType) => - fromType == typeof(TFrom) && toType == typeof(TTo) ? 100 : 0; - - public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result) + public string? Name { - if (from is TFrom typedFrom) - { - result = _converter(typedFrom)!; - return true; - } - - result = default(TTo); - return false; + get => _name; + set => this.RaiseAndSetIfChanged(ref _name, value); } } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/ByteToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/ByteToStringTypeConverterTests.cs index b47fa3a2db..095ff40a59 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/ByteToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/ByteToStringTypeConverterTests.cs @@ -8,28 +8,11 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class ByteToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_ByteToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new ByteToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(byte), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToByte_Returns10() - { - var converter = new ByteToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(byte)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new ByteToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] @@ -38,96 +21,45 @@ public async Task TryConvert_ByteToString_Succeeds() var converter = new ByteToStringTypeConverter(); byte value = 123; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("123"); } [Test] - public async Task TryConvert_StringToByte_Succeeds() - { - var converter = new ByteToStringTypeConverter(); - - var result = converter.TryConvert("123", typeof(byte), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo((byte)123); - } - - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new ByteToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(byte), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_OutOfRangeValue_ReturnsFalse() - { - var converter = new ByteToStringTypeConverter(); - - var result = converter.TryConvert("999", typeof(byte), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_NegativeValue_ReturnsFalse() - { - var converter = new ByteToStringTypeConverter(); - - var result = converter.TryConvert("-1", typeof(byte), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_WithConversionHint_FormatsCorrectly() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new ByteToStringTypeConverter(); - byte value = 5; + var value = byte.MaxValue; - var result = converter.TryConvert(value, typeof(string), 3, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("005"); + await Assert.That(output).IsEqualTo("255"); } [Test] public async Task TryConvert_MinValue_Succeeds() { var converter = new ByteToStringTypeConverter(); - byte value = byte.MinValue; + var value = byte.MinValue; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("0"); } [Test] - public async Task TryConvert_MaxValue_Succeeds() + public async Task TryConvert_WithConversionHint_FormatsCorrectly() { var converter = new ByteToStringTypeConverter(); - byte value = byte.MaxValue; + byte value = 5; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, 3, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("255"); - } - - [Test] - public async Task TryConvert_EmptyString_ReturnsFalse() - { - var converter = new ByteToStringTypeConverter(); - - var result = converter.TryConvert(string.Empty, typeof(byte), null, out var output); - - await Assert.That(result).IsFalse(); + await Assert.That(output).IsEqualTo("005"); } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/DecimalToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/DecimalToStringTypeConverterTests.cs index ded13f08ae..6267c3b6ac 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/DecimalToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/DecimalToStringTypeConverterTests.cs @@ -8,141 +8,130 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class DecimalToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_DecimalToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new DecimalToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(decimal), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToDecimal_Returns10() - { - var converter = new DecimalToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(decimal)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new DecimalToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] public async Task TryConvert_DecimalToString_Succeeds() { var converter = new DecimalToStringTypeConverter(); - decimal value = 123.456m; + var value = 123.456m; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("123.456"); } [Test] - public async Task TryConvert_StringToDecimal_Succeeds() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new DecimalToStringTypeConverter(); + var value = decimal.MaxValue; - var result = converter.TryConvert("123.456", typeof(decimal), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123.456m); + await Assert.That(output).IsEqualTo(decimal.MaxValue.ToString()); } [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() + public async Task TryConvert_MinValue_Succeeds() { var converter = new DecimalToStringTypeConverter(); + var value = decimal.MinValue; - var result = converter.TryConvert("invalid", typeof(decimal), null, out var output); + var result = converter.TryConvert(value, null, out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(decimal.MinValue.ToString()); } [Test] - public async Task TryConvert_WithConversionHint_FormatsCorrectly() + public async Task TryConvert_NegativeValue_Succeeds() { var converter = new DecimalToStringTypeConverter(); - decimal value = 42.5m; + var value = -123.456m; - var result = converter.TryConvert(value, typeof(string), 2, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("42.50"); + await Assert.That(output).IsEqualTo("-123.456"); } [Test] - public async Task TryConvert_MinValue_Succeeds() + public async Task TryConvert_WithConversionHint_FormatsCorrectly() { var converter = new DecimalToStringTypeConverter(); - decimal value = decimal.MinValue; + var value = 42.5m; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, 2, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(decimal.MinValue.ToString()); + await Assert.That(output).IsEqualTo("42.50"); } [Test] - public async Task TryConvert_MaxValue_Succeeds() + public async Task TryConvert_WithStringFormatHint_CurrencyFormat() { var converter = new DecimalToStringTypeConverter(); - decimal value = decimal.MaxValue; + var value = 1234.56m; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, "C", out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(decimal.MaxValue.ToString()); + await Assert.That(output).IsEqualTo(value.ToString("C")); } [Test] - public async Task TryConvert_Zero_Succeeds() + public async Task TryConvert_WithStringFormatHint_ExponentialFormat() { var converter = new DecimalToStringTypeConverter(); - decimal value = 0m; + var value = 1234.5678m; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, "E2", out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("0"); + await Assert.That(output).IsEqualTo(value.ToString("E2")); } [Test] - public async Task TryConvert_NegativeValue_Succeeds() + public async Task TryConvert_WithStringFormatHint_FormatsCorrectly() { var converter = new DecimalToStringTypeConverter(); - decimal value = -123.456m; + var value = 1234.5678m; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, "N2", out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("-123.456"); + await Assert.That(output).IsEqualTo(value.ToString("N2")); } [Test] - public async Task TryConvert_EmptyString_ReturnsFalse() + public async Task TryConvert_WithStringFormatHint_PercentFormat() { var converter = new DecimalToStringTypeConverter(); + var value = 0.1234m; - var result = converter.TryConvert(string.Empty, typeof(decimal), null, out var output); + var result = converter.TryConvert(value, "P2", out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(value.ToString("P2")); } [Test] - public async Task TryConvert_StringToDecimalWithRounding_RoundsCorrectly() + public async Task TryConvert_Zero_Succeeds() { var converter = new DecimalToStringTypeConverter(); + var value = 0m; - var result = converter.TryConvert("123.456789", typeof(decimal), 2, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123.46m); + await Assert.That(output).IsEqualTo("0"); } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/DoubleToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/DoubleToStringTypeConverterTests.cs index ca488ddf53..bf358cfdf0 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/DoubleToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/DoubleToStringTypeConverterTests.cs @@ -8,140 +8,118 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class DoubleToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_DoubleToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new DoubleToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(double), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToDouble_Returns10() - { - var converter = new DoubleToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(double)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new DoubleToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] public async Task TryConvert_DoubleToString_Succeeds() { var converter = new DoubleToStringTypeConverter(); - double value = 123.456; + var value = 123.456; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo(value.ToString()); } [Test] - public async Task TryConvert_StringToDouble_Succeeds() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new DoubleToStringTypeConverter(); + var value = double.MaxValue; - var result = converter.TryConvert("123.456", typeof(double), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123.456); - } - - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new DoubleToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(double), null, out var output); - - await Assert.That(result).IsFalse(); + await Assert.That(output).IsEqualTo(double.MaxValue.ToString()); } [Test] - public async Task TryConvert_WithConversionHint_FormatsCorrectly() + public async Task TryConvert_MinValue_Succeeds() { var converter = new DoubleToStringTypeConverter(); - double value = 42.5; + var value = double.MinValue; - var result = converter.TryConvert(value, typeof(string), 2, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("42.50"); + await Assert.That(output).IsEqualTo(double.MinValue.ToString()); } [Test] - public async Task TryConvert_MinValue_Succeeds() + public async Task TryConvert_NegativeValue_Succeeds() { var converter = new DoubleToStringTypeConverter(); - double value = double.MinValue; + var value = -123.456; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(double.MinValue.ToString()); + await Assert.That(output).IsEqualTo(value.ToString()); } [Test] - public async Task TryConvert_MaxValue_Succeeds() + public async Task TryConvert_WithConversionHint_FormatsCorrectly() { var converter = new DoubleToStringTypeConverter(); - double value = double.MaxValue; + var value = 42.5; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, 2, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(double.MaxValue.ToString()); + await Assert.That(output).IsEqualTo("42.50"); } [Test] - public async Task TryConvert_ScientificNotation_Succeeds() + public async Task TryConvert_WithStringFormatHint_CustomPrecision() { var converter = new DoubleToStringTypeConverter(); + var value = 0.123456789; - var result = converter.TryConvert("1.23E+10", typeof(double), null, out var output); + var result = converter.TryConvert(value, "0.0000", out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(1.23E+10); + await Assert.That(output).IsEqualTo(value.ToString("0.0000")); } [Test] - public async Task TryConvert_NegativeValue_Succeeds() + public async Task TryConvert_WithStringFormatHint_GeneralFormat() { var converter = new DoubleToStringTypeConverter(); - double value = -123.456; + var value = 123.456; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, "G", out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(value.ToString()); + await Assert.That(output).IsEqualTo(value.ToString("G")); } [Test] - public async Task TryConvert_EmptyString_ReturnsFalse() + public async Task TryConvert_WithStringFormatHint_RoundTripFormat() { var converter = new DoubleToStringTypeConverter(); + var value = 123.456789012345; - var result = converter.TryConvert(string.Empty, typeof(double), null, out var output); + var result = converter.TryConvert(value, "R", out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(value.ToString("R")); } [Test] - public async Task TryConvert_StringToDoubleWithRounding_RoundsCorrectly() + public async Task TryConvert_WithStringFormatHint_ScientificFormat() { var converter = new DoubleToStringTypeConverter(); + var value = 12345.6789; - var result = converter.TryConvert("123.456789", typeof(double), 2, out var output); + var result = converter.TryConvert(value, "E3", out var output); await Assert.That(result).IsTrue(); - await Assert.That((double)output!).IsEqualTo(123.46).Within(0.01); + await Assert.That(output).IsEqualTo(value.ToString("E3")); } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/EqualityTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/EqualityTypeConverterTests.cs index c454b413c3..4f647922a6 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/EqualityTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/EqualityTypeConverterTests.cs @@ -5,172 +5,164 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; +/// +/// Tests for the EqualityTypeConverter which compares objects for equality. +/// public class EqualityTypeConverterTests { [Test] - public async Task GetAffinityForObjects_AssignableTypes_Returns100() + public async Task FromType_ReturnsObjectType() { var converter = new EqualityTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(object)); - await Assert.That(affinity).IsEqualTo(100); + await Assert.That(converter.FromType).IsEqualTo(typeof(object)); } [Test] - public async Task GetAffinityForObjects_SameType_Returns100() + public async Task GetAffinityForObjects_Returns1() { var converter = new EqualityTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(int), typeof(int)); - await Assert.That(affinity).IsEqualTo(100); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(1); } [Test] - public async Task GetAffinityForObjects_ObjectType_Returns100() + public async Task ToType_ReturnsBoolType() { var converter = new EqualityTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(object), typeof(string)); - await Assert.That(affinity).IsEqualTo(100); + await Assert.That(converter.ToType).IsEqualTo(typeof(bool)); } [Test] - public async Task GetAffinityForObjects_NullableTypes_ReturnsAffinity() + public async Task TryConvertTyped_BothNull_ReturnsTrue() { var converter = new EqualityTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(int?), typeof(int)); - await Assert.That(affinity).IsGreaterThan(0); - } - [Test] - public async Task GetAffinityForObjects_ToNullableTypes_ReturnsAffinity() - { - var converter = new EqualityTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(int), typeof(int?)); - await Assert.That(affinity).IsGreaterThan(0); - } + var result = converter.TryConvertTyped(null, null, out var output); - [Test] - public async Task GetAffinityForObjects_IncompatibleTypes_Returns0() - { - var converter = new EqualityTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(int), typeof(string)); - await Assert.That(affinity).IsEqualTo(0); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(true); } [Test] - public async Task TryConvert_SameType_Succeeds() + public async Task TryConvertTyped_DifferentIntegers_ReturnsFalse() { var converter = new EqualityTypeConverter(); - var value = "test"; + var obj = 42; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvertTyped(obj, 43, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(value); + await Assert.That(output).IsEqualTo(false); } [Test] - public async Task TryConvert_AssignableType_Succeeds() + public async Task TryConvertTyped_DifferentTypes_ReturnsFalse() { var converter = new EqualityTypeConverter(); - var value = "test"; + var obj = "42"; - var result = converter.TryConvert(value, typeof(object), null, out var output); + var result = converter.TryConvertTyped(obj, 42, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(value); + await Assert.That(output).IsEqualTo(false); } [Test] - public async Task TryConvert_NullToReferenceType_Succeeds() + public async Task TryConvertTyped_DifferentValues_ReturnsFalse() { var converter = new EqualityTypeConverter(); + var obj = "test"; - var result = converter.TryConvert(null, typeof(string), null, out var output); + var result = converter.TryConvertTyped(obj, "other", out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsNull(); + await Assert.That(output).IsEqualTo(false); } [Test] - public async Task TryConvert_NullToNullableValueType_Succeeds() + public async Task TryConvertTyped_EqualIntegers_ReturnsTrue() { var converter = new EqualityTypeConverter(); + var obj = 42; - var result = converter.TryConvert(null, typeof(int?), null, out var output); + var result = converter.TryConvertTyped(obj, 42, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsNull(); + await Assert.That(output).IsEqualTo(true); } [Test] - public async Task TryConvert_NullToValueType_Fails() + public async Task TryConvertTyped_EqualStrings_ReturnsTrue() { var converter = new EqualityTypeConverter(); + var obj = "hello"; - var result = converter.TryConvert(null, typeof(int), null, out var output); + var result = converter.TryConvertTyped(obj, "hello", out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(true); } [Test] - public async Task TryConvert_IncompatibleTypes_Fails() + public async Task TryConvertTyped_EqualValues_ReturnsTrue() { var converter = new EqualityTypeConverter(); - var value = "test"; + var obj = "test"; - var result = converter.TryConvert(value, typeof(int), null, out var output); + var result = converter.TryConvertTyped(obj, "test", out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(true); } [Test] - public async Task TryConvert_ValueToNullableType_Succeeds() + public async Task TryConvertTyped_NoConversionHint_UseNullComparison() { var converter = new EqualityTypeConverter(); - int value = 42; + var obj = "test"; - var result = converter.TryConvert(value, typeof(int?), null, out var output); + var result = converter.TryConvertTyped(obj, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(42); + await Assert.That(output).IsEqualTo(false); } [Test] - public async Task DoReferenceCast_NullToReferenceType_ReturnsNull() + public async Task TryConvertTyped_OneNull_ReturnsFalse() { - var result = EqualityTypeConverter.DoReferenceCast(null, typeof(string)); - await Assert.That(result).IsNull(); - } + var converter = new EqualityTypeConverter(); - [Test] - public void DoReferenceCast_NullToValueType_Throws() - { - Assert.Throws(() => EqualityTypeConverter.DoReferenceCast(null, typeof(int))); - } + var result1 = converter.TryConvertTyped("test", null, out var output1); + var result2 = converter.TryConvertTyped(null, "test", out var output2); - [Test] - public async Task DoReferenceCast_SameType_ReturnsValue() - { - var value = "test"; - var result = EqualityTypeConverter.DoReferenceCast(value, typeof(string)); - await Assert.That(result).IsEqualTo(value); + await Assert.That(result1).IsTrue(); + await Assert.That(output1).IsEqualTo(false); + await Assert.That(result2).IsTrue(); + await Assert.That(output2).IsEqualTo(false); } [Test] - public async Task DoReferenceCast_NullToNullableType_ReturnsNull() + public async Task TryConvertTyped_ReferenceEquality_ReturnsTrue() { - var result = EqualityTypeConverter.DoReferenceCast(null, typeof(int?)); - await Assert.That(result).IsNull(); - } + var converter = new EqualityTypeConverter(); + var obj = new object(); - [Test] - public void DoReferenceCast_IncompatibleTypes_Throws() - { - Assert.Throws(() => EqualityTypeConverter.DoReferenceCast("test", typeof(int))); + var result = converter.TryConvertTyped(obj, obj, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(true); } [Test] - public void DoReferenceCast_NullTargetType_Throws() + public async Task TryConvertTyped_ValueEquality_ReturnsTrue() { - Assert.Throws(() => EqualityTypeConverter.DoReferenceCast("test", null!)); + var converter = new EqualityTypeConverter(); + var obj = new { Value = "test" }; + var other = new { Value = "test" }; + + var result = converter.TryConvertTyped(obj, other, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(true); } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/IntegerToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/IntegerToStringTypeConverterTests.cs index df48da3884..b1cffd0060 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/IntegerToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/IntegerToStringTypeConverterTests.cs @@ -5,131 +5,124 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; +/// +/// Tests for converting integers to strings. +/// public class IntegerToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_IntToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new IntegerToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(int), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToInt_Returns10() - { - var converter = new IntegerToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(int)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new IntegerToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(long), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(long))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] public async Task TryConvert_IntToString_Succeeds() { var converter = new IntegerToStringTypeConverter(); - int value = 123456; + var value = 123456; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("123456"); } [Test] - public async Task TryConvert_StringToInt_Succeeds() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new IntegerToStringTypeConverter(); + var value = int.MaxValue; - var result = converter.TryConvert("123456", typeof(int), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123456); + await Assert.That(output).IsEqualTo(int.MaxValue.ToString()); } [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() + public async Task TryConvert_MinValue_Succeeds() { var converter = new IntegerToStringTypeConverter(); + var value = int.MinValue; - var result = converter.TryConvert("invalid", typeof(int), null, out var output); + var result = converter.TryConvert(value, null, out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(int.MinValue.ToString()); } [Test] - public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + public async Task TryConvert_NegativeValue_Succeeds() { var converter = new IntegerToStringTypeConverter(); + var value = -123456; - var result = converter.TryConvert("9999999999", typeof(int), null, out var output); + var result = converter.TryConvert(value, null, out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("-123456"); } [Test] public async Task TryConvert_WithConversionHint_FormatsCorrectly() { var converter = new IntegerToStringTypeConverter(); - int value = 42; + var value = 42; - var result = converter.TryConvert(value, typeof(string), 8, out var output); + var result = converter.TryConvert(value, 8, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("00000042"); } [Test] - public async Task TryConvert_MinValue_Succeeds() + public async Task TryConvert_WithStringFormatHint_CustomFormat() { var converter = new IntegerToStringTypeConverter(); - int value = int.MinValue; + var value = 42; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, "000", out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(int.MinValue.ToString()); + await Assert.That(output).IsEqualTo("042"); } [Test] - public async Task TryConvert_MaxValue_Succeeds() + public async Task TryConvert_WithStringFormatHint_HexFormat() { var converter = new IntegerToStringTypeConverter(); - int value = int.MaxValue; + var value = 255; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, "X", out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(int.MaxValue.ToString()); + await Assert.That(output).IsEqualTo("FF"); } [Test] - public async Task TryConvert_EmptyString_ReturnsFalse() + public async Task TryConvert_WithStringFormatHint_HexFormatLowercase() { var converter = new IntegerToStringTypeConverter(); + var value = 255; - var result = converter.TryConvert(string.Empty, typeof(int), null, out var output); + var result = converter.TryConvert(value, "x8", out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("000000ff"); } [Test] - public async Task TryConvert_NegativeValue_Succeeds() + public async Task TryConvert_WithStringFormatHint_NumberFormat() { var converter = new IntegerToStringTypeConverter(); - int value = -123456; + var value = 1234567; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, "N0", out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("-123456"); + await Assert.That(output).IsEqualTo(value.ToString("N0")); } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/LongToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/LongToStringTypeConverterTests.cs index f9c14d6ed3..ef34336455 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/LongToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/LongToStringTypeConverterTests.cs @@ -8,116 +8,58 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class LongToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_LongToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new LongToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(long), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToLong_Returns10() - { - var converter = new LongToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(long)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new LongToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] public async Task TryConvert_LongToString_Succeeds() { var converter = new LongToStringTypeConverter(); - long value = 123456789012; + var value = 123456789012; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("123456789012"); } [Test] - public async Task TryConvert_StringToLong_Succeeds() - { - var converter = new LongToStringTypeConverter(); - - var result = converter.TryConvert("123456789012", typeof(long), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123456789012L); - } - - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new LongToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(long), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_OutOfRangeValue_ReturnsFalse() - { - var converter = new LongToStringTypeConverter(); - - var result = converter.TryConvert("99999999999999999999", typeof(long), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_WithConversionHint_FormatsCorrectly() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new LongToStringTypeConverter(); - long value = 42; + var value = long.MaxValue; - var result = converter.TryConvert(value, typeof(string), 10, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("0000000042"); + await Assert.That(output).IsEqualTo(long.MaxValue.ToString()); } [Test] public async Task TryConvert_MinValue_Succeeds() { var converter = new LongToStringTypeConverter(); - long value = long.MinValue; + var value = long.MinValue; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo(long.MinValue.ToString()); } [Test] - public async Task TryConvert_MaxValue_Succeeds() + public async Task TryConvert_WithConversionHint_FormatsCorrectly() { var converter = new LongToStringTypeConverter(); - long value = long.MaxValue; + long value = 42; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, 10, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(long.MaxValue.ToString()); - } - - [Test] - public async Task TryConvert_EmptyString_ReturnsFalse() - { - var converter = new LongToStringTypeConverter(); - - var result = converter.TryConvert(string.Empty, typeof(long), null, out var output); - - await Assert.That(result).IsFalse(); + await Assert.That(output).IsEqualTo("0000000042"); } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableByteToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableByteToStringTypeConverterTests.cs index c07dbdc8cb..931dd7e1b7 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableByteToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableByteToStringTypeConverterTests.cs @@ -8,29 +8,11 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class NullableByteToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_ByteNullableToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new NullableByteToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(byte?), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToByteNullable_Returns10() - { - var converter = new NullableByteToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(byte?)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new NullableByteToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(byte), typeof(string))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] @@ -39,81 +21,45 @@ public async Task TryConvert_ByteNullableToString_Succeeds() var converter = new NullableByteToStringTypeConverter(); byte? value = 123; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("123"); } [Test] - public async Task TryConvert_StringToByteNullable_Succeeds() - { - var converter = new NullableByteToStringTypeConverter(); - - var result = converter.TryConvert("123", typeof(byte?), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo((byte)123); - } - - [Test] - public async Task TryConvert_NullValue_ReturnsTrue() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new NullableByteToStringTypeConverter(); + byte? value = byte.MaxValue; - var result = converter.TryConvert(null, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("255"); } [Test] - public async Task TryConvert_EmptyString_ReturnsTrue() + public async Task TryConvert_MinValue_Succeeds() { var converter = new NullableByteToStringTypeConverter(); + byte? value = byte.MinValue; - var result = converter.TryConvert(string.Empty, typeof(byte?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("0"); } [Test] - public async Task TryConvert_NullString_ReturnsTrue() + public async Task TryConvert_NullValue_ReturnsTrue() { var converter = new NullableByteToStringTypeConverter(); - var result = converter.TryConvert(null, typeof(byte?), null, out var output); + var result = converter.TryConvert(null, null, out var output); await Assert.That(result).IsTrue(); - } - - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new NullableByteToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(byte?), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_OutOfRangeValue_ReturnsFalse() - { - var converter = new NullableByteToStringTypeConverter(); - - var result = converter.TryConvert("999", typeof(byte?), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_NegativeValue_ReturnsFalse() - { - var converter = new NullableByteToStringTypeConverter(); - - var result = converter.TryConvert("-1", typeof(byte?), null, out var output); - - await Assert.That(result).IsFalse(); + await Assert.That(output).IsNull(); } [Test] @@ -122,33 +68,9 @@ public async Task TryConvert_WithConversionHint_FormatsCorrectly() var converter = new NullableByteToStringTypeConverter(); byte? value = 5; - var result = converter.TryConvert(value, typeof(string), 3, out var output); + var result = converter.TryConvert(value, 3, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("005"); } - - [Test] - public async Task TryConvert_MinValue_Succeeds() - { - var converter = new NullableByteToStringTypeConverter(); - byte? value = byte.MinValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("0"); - } - - [Test] - public async Task TryConvert_MaxValue_Succeeds() - { - var converter = new NullableByteToStringTypeConverter(); - byte? value = byte.MaxValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("255"); - } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableDecimalToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableDecimalToStringTypeConverterTests.cs index f7fa7dbf27..ad1b3722a6 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableDecimalToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableDecimalToStringTypeConverterTests.cs @@ -8,29 +8,11 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class NullableDecimalToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_DecimalNullableToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new NullableDecimalToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(decimal?), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToDecimalNullable_Returns10() - { - var converter = new NullableDecimalToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(decimal?)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new NullableDecimalToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(decimal), typeof(string))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] @@ -39,51 +21,56 @@ public async Task TryConvert_DecimalNullableToString_Succeeds() var converter = new NullableDecimalToStringTypeConverter(); decimal? value = 123.456m; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("123.456"); } [Test] - public async Task TryConvert_StringToDecimalNullable_Succeeds() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new NullableDecimalToStringTypeConverter(); + decimal? value = decimal.MaxValue; - var result = converter.TryConvert("123.456", typeof(decimal?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123.456m); + await Assert.That(output).IsEqualTo(decimal.MaxValue.ToString()); } [Test] - public async Task TryConvert_NullValue_ReturnsTrue() + public async Task TryConvert_MinValue_Succeeds() { var converter = new NullableDecimalToStringTypeConverter(); + decimal? value = decimal.MinValue; - var result = converter.TryConvert(null, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(decimal.MinValue.ToString()); } [Test] - public async Task TryConvert_EmptyString_ReturnsTrue() + public async Task TryConvert_NegativeValue_Succeeds() { var converter = new NullableDecimalToStringTypeConverter(); + decimal? value = -123.456m; - var result = converter.TryConvert(string.Empty, typeof(decimal?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("-123.456"); } [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() + public async Task TryConvert_NullValue_ReturnsTrue() { var converter = new NullableDecimalToStringTypeConverter(); - var result = converter.TryConvert("invalid", typeof(decimal?), null, out var output); + var result = converter.TryConvert(null, null, out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); } [Test] @@ -92,68 +79,21 @@ public async Task TryConvert_WithConversionHint_FormatsCorrectly() var converter = new NullableDecimalToStringTypeConverter(); decimal? value = 42.5m; - var result = converter.TryConvert(value, typeof(string), 2, out var output); + var result = converter.TryConvert(value, 2, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("42.50"); } - [Test] - public async Task TryConvert_MinValue_Succeeds() - { - var converter = new NullableDecimalToStringTypeConverter(); - decimal? value = decimal.MinValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(decimal.MinValue.ToString()); - } - - [Test] - public async Task TryConvert_MaxValue_Succeeds() - { - var converter = new NullableDecimalToStringTypeConverter(); - decimal? value = decimal.MaxValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(decimal.MaxValue.ToString()); - } - [Test] public async Task TryConvert_Zero_Succeeds() { var converter = new NullableDecimalToStringTypeConverter(); decimal? value = 0m; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("0"); } - - [Test] - public async Task TryConvert_NegativeValue_Succeeds() - { - var converter = new NullableDecimalToStringTypeConverter(); - decimal? value = -123.456m; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("-123.456"); - } - - [Test] - public async Task TryConvert_StringToDecimalWithRounding_RoundsCorrectly() - { - var converter = new NullableDecimalToStringTypeConverter(); - - var result = converter.TryConvert("123.456789", typeof(decimal?), 2, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123.46m); - } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableDoubleToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableDoubleToStringTypeConverterTests.cs index 3a5f20dd77..752e947c8f 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableDoubleToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableDoubleToStringTypeConverterTests.cs @@ -8,29 +8,11 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class NullableDoubleToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_DoubleNullableToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new NullableDoubleToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(double?), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToDoubleNullable_Returns10() - { - var converter = new NullableDoubleToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(double?)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new NullableDoubleToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(double), typeof(string))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] @@ -39,63 +21,22 @@ public async Task TryConvert_DoubleNullableToString_Succeeds() var converter = new NullableDoubleToStringTypeConverter(); double? value = 123.456; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo(value.ToString()); } [Test] - public async Task TryConvert_StringToDoubleNullable_Succeeds() - { - var converter = new NullableDoubleToStringTypeConverter(); - - var result = converter.TryConvert("123.456", typeof(double?), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123.456); - } - - [Test] - public async Task TryConvert_NullValue_ReturnsTrue() - { - var converter = new NullableDoubleToStringTypeConverter(); - - var result = converter.TryConvert(null, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - } - - [Test] - public async Task TryConvert_EmptyString_ReturnsTrue() - { - var converter = new NullableDoubleToStringTypeConverter(); - - var result = converter.TryConvert(string.Empty, typeof(double?), null, out var output); - - await Assert.That(result).IsTrue(); - } - - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new NullableDoubleToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(double?), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_WithConversionHint_FormatsCorrectly() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new NullableDoubleToStringTypeConverter(); - double? value = 42.5; + double? value = double.MaxValue; - var result = converter.TryConvert(value, typeof(string), 2, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("42.50"); + await Assert.That(output).IsEqualTo(double.MaxValue.ToString()); } [Test] @@ -104,55 +45,43 @@ public async Task TryConvert_MinValue_Succeeds() var converter = new NullableDoubleToStringTypeConverter(); double? value = double.MinValue; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo(double.MinValue.ToString()); } [Test] - public async Task TryConvert_MaxValue_Succeeds() - { - var converter = new NullableDoubleToStringTypeConverter(); - double? value = double.MaxValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(double.MaxValue.ToString()); - } - - [Test] - public async Task TryConvert_ScientificNotation_Succeeds() + public async Task TryConvert_NegativeValue_Succeeds() { var converter = new NullableDoubleToStringTypeConverter(); + double? value = -123.456; - var result = converter.TryConvert("1.23E+10", typeof(double?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(1.23E+10); + await Assert.That(output).IsEqualTo(value.ToString()); } [Test] - public async Task TryConvert_NegativeValue_Succeeds() + public async Task TryConvert_NullValue_ReturnsTrue() { var converter = new NullableDoubleToStringTypeConverter(); - double? value = -123.456; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(null, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(value.ToString()); } [Test] - public async Task TryConvert_StringToDoubleWithRounding_RoundsCorrectly() + public async Task TryConvert_WithConversionHint_FormatsCorrectly() { var converter = new NullableDoubleToStringTypeConverter(); + double? value = 42.5; - var result = converter.TryConvert("123.456789", typeof(double?), 2, out var output); + var result = converter.TryConvert(value, 2, out var output); await Assert.That(result).IsTrue(); - await Assert.That((double)output!).IsEqualTo(123.46).Within(0.01); + await Assert.That(output).IsEqualTo("42.50"); } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableIntegerToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableIntegerToStringTypeConverterTests.cs index 2656c3db8a..cc93225ec8 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableIntegerToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableIntegerToStringTypeConverterTests.cs @@ -8,29 +8,11 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class NullableIntegerToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_IntNullableToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new NullableIntegerToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(int?), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToIntNullable_Returns10() - { - var converter = new NullableIntegerToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(int?)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new NullableIntegerToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(long), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(long))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] @@ -39,96 +21,55 @@ public async Task TryConvert_IntNullableToString_Succeeds() var converter = new NullableIntegerToStringTypeConverter(); int? value = 123456; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("123456"); } [Test] - public async Task TryConvert_StringToIntNullable_Succeeds() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new NullableIntegerToStringTypeConverter(); + int? value = int.MaxValue; - var result = converter.TryConvert("123456", typeof(int?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123456); + await Assert.That(output).IsEqualTo(int.MaxValue.ToString()); } [Test] - public async Task TryConvert_NullValue_ReturnsTrue() + public async Task TryConvert_MinValue_Succeeds() { var converter = new NullableIntegerToStringTypeConverter(); + int? value = int.MinValue; - var result = converter.TryConvert(null, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(int.MinValue.ToString()); } [Test] - public async Task TryConvert_EmptyString_ReturnsTrue() + public async Task TryConvert_NullValue_ReturnsTrue() { var converter = new NullableIntegerToStringTypeConverter(); - var result = converter.TryConvert(string.Empty, typeof(int?), null, out var output); + var result = converter.TryConvert(null, null, out var output); await Assert.That(result).IsTrue(); } - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new NullableIntegerToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(int?), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_OutOfRangeValue_ReturnsFalse() - { - var converter = new NullableIntegerToStringTypeConverter(); - - var result = converter.TryConvert("9999999999", typeof(int?), null, out var output); - - await Assert.That(result).IsFalse(); - } - [Test] public async Task TryConvert_WithConversionHint_FormatsCorrectly() { var converter = new NullableIntegerToStringTypeConverter(); int? value = 42; - var result = converter.TryConvert(value, typeof(string), 8, out var output); + var result = converter.TryConvert(value, 8, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("00000042"); } - - [Test] - public async Task TryConvert_MinValue_Succeeds() - { - var converter = new NullableIntegerToStringTypeConverter(); - int? value = int.MinValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(int.MinValue.ToString()); - } - - [Test] - public async Task TryConvert_MaxValue_Succeeds() - { - var converter = new NullableIntegerToStringTypeConverter(); - int? value = int.MaxValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(int.MaxValue.ToString()); - } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableLongToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableLongToStringTypeConverterTests.cs index 53b548d1e4..36d9c52c18 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableLongToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableLongToStringTypeConverterTests.cs @@ -8,29 +8,11 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class NullableLongToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_LongNullableToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new NullableLongToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(long?), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_StringToLongNullable_Returns10() - { - var converter = new NullableLongToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(long?)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new NullableLongToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(long), typeof(string))).IsEqualTo(0); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] @@ -39,96 +21,55 @@ public async Task TryConvert_LongNullableToString_Succeeds() var converter = new NullableLongToStringTypeConverter(); long? value = 123456789012; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("123456789012"); } [Test] - public async Task TryConvert_StringToLongNullable_Succeeds() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new NullableLongToStringTypeConverter(); + long? value = long.MaxValue; - var result = converter.TryConvert("123456789012", typeof(long?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(123456789012L); + await Assert.That(output).IsEqualTo(long.MaxValue.ToString()); } [Test] - public async Task TryConvert_NullValue_ReturnsTrue() + public async Task TryConvert_MinValue_Succeeds() { var converter = new NullableLongToStringTypeConverter(); + long? value = long.MinValue; - var result = converter.TryConvert(null, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(long.MinValue.ToString()); } [Test] - public async Task TryConvert_EmptyString_ReturnsTrue() + public async Task TryConvert_NullValue_ReturnsTrue() { var converter = new NullableLongToStringTypeConverter(); - var result = converter.TryConvert(string.Empty, typeof(long?), null, out var output); + var result = converter.TryConvert(null, null, out var output); await Assert.That(result).IsTrue(); } - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new NullableLongToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(long?), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_OutOfRangeValue_ReturnsFalse() - { - var converter = new NullableLongToStringTypeConverter(); - - var result = converter.TryConvert("99999999999999999999", typeof(long?), null, out var output); - - await Assert.That(result).IsFalse(); - } - [Test] public async Task TryConvert_WithConversionHint_FormatsCorrectly() { var converter = new NullableLongToStringTypeConverter(); long? value = 42; - var result = converter.TryConvert(value, typeof(string), 10, out var output); + var result = converter.TryConvert(value, 10, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("0000000042"); } - - [Test] - public async Task TryConvert_MinValue_Succeeds() - { - var converter = new NullableLongToStringTypeConverter(); - long? value = long.MinValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(long.MinValue.ToString()); - } - - [Test] - public async Task TryConvert_MaxValue_Succeeds() - { - var converter = new NullableLongToStringTypeConverter(); - long? value = long.MaxValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(long.MaxValue.ToString()); - } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableShortToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableShortToStringTypeConverterTests.cs index a1a2a5a5c8..b7cac49755 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableShortToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableShortToStringTypeConverterTests.cs @@ -8,52 +8,35 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class NullableShortToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_ShortNullableToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new NullableShortToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(short?), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] - public async Task GetAffinityForObjects_StringToShortNullable_Returns10() - { - var converter = new NullableShortToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(short?)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new NullableShortToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(short), typeof(string))).IsEqualTo(0); - } - - [Test] - public async Task TryConvert_ShortNullableToString_Succeeds() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new NullableShortToStringTypeConverter(); - short? value = 12345; + short? value = short.MaxValue; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("12345"); + await Assert.That(output).IsEqualTo(short.MaxValue.ToString()); } [Test] - public async Task TryConvert_StringToShortNullable_Succeeds() + public async Task TryConvert_MinValue_Succeeds() { var converter = new NullableShortToStringTypeConverter(); + short? value = short.MinValue; - var result = converter.TryConvert("12345", typeof(short?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo((short)12345); + await Assert.That(output).IsEqualTo(short.MinValue.ToString()); } [Test] @@ -61,39 +44,21 @@ public async Task TryConvert_NullValue_ReturnsTrue() { var converter = new NullableShortToStringTypeConverter(); - var result = converter.TryConvert(null, typeof(string), null, out var output); + var result = converter.TryConvert(null, null, out var output); await Assert.That(result).IsTrue(); } [Test] - public async Task TryConvert_EmptyString_ReturnsTrue() + public async Task TryConvert_ShortNullableToString_Succeeds() { var converter = new NullableShortToStringTypeConverter(); + short? value = 12345; - var result = converter.TryConvert(string.Empty, typeof(short?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - } - - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new NullableShortToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(short?), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_OutOfRangeValue_ReturnsFalse() - { - var converter = new NullableShortToStringTypeConverter(); - - var result = converter.TryConvert("99999", typeof(short?), null, out var output); - - await Assert.That(result).IsFalse(); + await Assert.That(output).IsEqualTo("12345"); } [Test] @@ -102,33 +67,9 @@ public async Task TryConvert_WithConversionHint_FormatsCorrectly() var converter = new NullableShortToStringTypeConverter(); short? value = 42; - var result = converter.TryConvert(value, typeof(string), 5, out var output); + var result = converter.TryConvert(value, 5, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("00042"); } - - [Test] - public async Task TryConvert_MinValue_Succeeds() - { - var converter = new NullableShortToStringTypeConverter(); - short? value = short.MinValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(short.MinValue.ToString()); - } - - [Test] - public async Task TryConvert_MaxValue_Succeeds() - { - var converter = new NullableShortToStringTypeConverter(); - short? value = short.MaxValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(short.MaxValue.ToString()); - } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableSingleToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableSingleToStringTypeConverterTests.cs index 91f55fc944..50b7bcaa10 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableSingleToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/NullableSingleToStringTypeConverterTests.cs @@ -8,52 +8,47 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class NullableSingleToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_SingleNullableToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new NullableSingleToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(float?), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] - public async Task GetAffinityForObjects_StringToSingleNullable_Returns10() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new NullableSingleToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(float?)); - await Assert.That(affinity).IsEqualTo(10); - } + float? value = float.MaxValue; - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new NullableSingleToStringTypeConverter(); + var result = converter.TryConvert(value, null, out var output); - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(float), typeof(string))).IsEqualTo(0); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(float.MaxValue.ToString()); } [Test] - public async Task TryConvert_SingleNullableToString_Succeeds() + public async Task TryConvert_MinValue_Succeeds() { var converter = new NullableSingleToStringTypeConverter(); - float? value = 123.456f; + float? value = float.MinValue; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(value.ToString()); + await Assert.That(output).IsEqualTo(float.MinValue.ToString()); } [Test] - public async Task TryConvert_StringToSingleNullable_Succeeds() + public async Task TryConvert_NegativeValue_Succeeds() { var converter = new NullableSingleToStringTypeConverter(); + float? value = -123.456f; - var result = converter.TryConvert("123.456", typeof(float?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That((float)output!).IsEqualTo(123.456f).Within(0.001f); + await Assert.That(output).IsEqualTo(value.ToString()); } [Test] @@ -61,29 +56,21 @@ public async Task TryConvert_NullValue_ReturnsTrue() { var converter = new NullableSingleToStringTypeConverter(); - var result = converter.TryConvert(null, typeof(string), null, out var output); + var result = converter.TryConvert(null, null, out var output); await Assert.That(result).IsTrue(); } [Test] - public async Task TryConvert_EmptyString_ReturnsTrue() + public async Task TryConvert_SingleNullableToString_Succeeds() { var converter = new NullableSingleToStringTypeConverter(); + float? value = 123.456f; - var result = converter.TryConvert(string.Empty, typeof(float?), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - } - - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new NullableSingleToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(float?), null, out var output); - - await Assert.That(result).IsFalse(); + await Assert.That(output).IsEqualTo(value.ToString()); } [Test] @@ -92,56 +79,9 @@ public async Task TryConvert_WithConversionHint_FormatsCorrectly() var converter = new NullableSingleToStringTypeConverter(); float? value = 42.5f; - var result = converter.TryConvert(value, typeof(string), 2, out var output); + var result = converter.TryConvert(value, 2, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("42.50"); } - - [Test] - public async Task TryConvert_MinValue_Succeeds() - { - var converter = new NullableSingleToStringTypeConverter(); - float? value = float.MinValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(float.MinValue.ToString()); - } - - [Test] - public async Task TryConvert_MaxValue_Succeeds() - { - var converter = new NullableSingleToStringTypeConverter(); - float? value = float.MaxValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(float.MaxValue.ToString()); - } - - [Test] - public async Task TryConvert_NegativeValue_Succeeds() - { - var converter = new NullableSingleToStringTypeConverter(); - float? value = -123.456f; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(value.ToString()); - } - - [Test] - public async Task TryConvert_StringToSingleWithRounding_RoundsCorrectly() - { - var converter = new NullableSingleToStringTypeConverter(); - - var result = converter.TryConvert("123.456789", typeof(float?), 2, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That((float)output!).IsEqualTo(123.46f).Within(0.01f); - } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/ShortToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/ShortToStringTypeConverterTests.cs index 03066182df..a9420daa40 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/ShortToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/ShortToStringTypeConverterTests.cs @@ -8,71 +8,47 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class ShortToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_ShortToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new ShortToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(short), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] - public async Task GetAffinityForObjects_StringToShort_Returns10() - { - var converter = new ShortToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(short)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new ShortToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); - } - - [Test] - public async Task TryConvert_ShortToString_Succeeds() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new ShortToStringTypeConverter(); - short value = 12345; + var value = short.MaxValue; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("12345"); + await Assert.That(output).IsEqualTo(short.MaxValue.ToString()); } [Test] - public async Task TryConvert_StringToShort_Succeeds() + public async Task TryConvert_MinValue_Succeeds() { var converter = new ShortToStringTypeConverter(); + var value = short.MinValue; - var result = converter.TryConvert("12345", typeof(short), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo((short)12345); - } - - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new ShortToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(short), null, out var output); - - await Assert.That(result).IsFalse(); + await Assert.That(output).IsEqualTo(short.MinValue.ToString()); } [Test] - public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + public async Task TryConvert_ShortToString_Succeeds() { var converter = new ShortToStringTypeConverter(); + short value = 12345; - var result = converter.TryConvert("99999", typeof(short), null, out var output); + var result = converter.TryConvert(value, null, out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo("12345"); } [Test] @@ -81,43 +57,9 @@ public async Task TryConvert_WithConversionHint_FormatsCorrectly() var converter = new ShortToStringTypeConverter(); short value = 42; - var result = converter.TryConvert(value, typeof(string), 5, out var output); + var result = converter.TryConvert(value, 5, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("00042"); } - - [Test] - public async Task TryConvert_MinValue_Succeeds() - { - var converter = new ShortToStringTypeConverter(); - short value = short.MinValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(short.MinValue.ToString()); - } - - [Test] - public async Task TryConvert_MaxValue_Succeeds() - { - var converter = new ShortToStringTypeConverter(); - short value = short.MaxValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(short.MaxValue.ToString()); - } - - [Test] - public async Task TryConvert_EmptyString_ReturnsFalse() - { - var converter = new ShortToStringTypeConverter(); - - var result = converter.TryConvert(string.Empty, typeof(short), null, out var output); - - await Assert.That(result).IsFalse(); - } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/SingleToStringTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/SingleToStringTypeConverterTests.cs index ff9baa2008..0ea4c046f3 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/SingleToStringTypeConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/SingleToStringTypeConverterTests.cs @@ -8,129 +8,70 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class SingleToStringTypeConverterTests { [Test] - public async Task GetAffinityForObjects_SingleToString_Returns10() + public async Task GetAffinityForObjects_Returns2() { var converter = new SingleToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(float), typeof(string)); - await Assert.That(affinity).IsEqualTo(10); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); } [Test] - public async Task GetAffinityForObjects_StringToSingle_Returns10() - { - var converter = new SingleToStringTypeConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(float)); - await Assert.That(affinity).IsEqualTo(10); - } - - [Test] - public async Task GetAffinityForObjects_WrongTypes_Returns0() - { - var converter = new SingleToStringTypeConverter(); - - await Assert.That(converter.GetAffinityForObjects(typeof(int), typeof(string))).IsEqualTo(0); - await Assert.That(converter.GetAffinityForObjects(typeof(string), typeof(int))).IsEqualTo(0); - } - - [Test] - public async Task TryConvert_SingleToString_Succeeds() - { - var converter = new SingleToStringTypeConverter(); - float value = 123.456f; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(value.ToString()); - } - - [Test] - public async Task TryConvert_StringToSingle_Succeeds() - { - var converter = new SingleToStringTypeConverter(); - - var result = converter.TryConvert("123.456", typeof(float), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That((float)output!).IsEqualTo(123.456f).Within(0.001f); - } - - [Test] - public async Task TryConvert_InvalidString_ReturnsFalse() - { - var converter = new SingleToStringTypeConverter(); - - var result = converter.TryConvert("invalid", typeof(float), null, out var output); - - await Assert.That(result).IsFalse(); - } - - [Test] - public async Task TryConvert_WithConversionHint_FormatsCorrectly() + public async Task TryConvert_MaxValue_Succeeds() { var converter = new SingleToStringTypeConverter(); - float value = 42.5f; + var value = float.MaxValue; - var result = converter.TryConvert(value, typeof(string), 2, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("42.50"); + await Assert.That(output).IsEqualTo(float.MaxValue.ToString()); } [Test] public async Task TryConvert_MinValue_Succeeds() { var converter = new SingleToStringTypeConverter(); - float value = float.MinValue; + var value = float.MinValue; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo(float.MinValue.ToString()); } - [Test] - public async Task TryConvert_MaxValue_Succeeds() - { - var converter = new SingleToStringTypeConverter(); - float value = float.MaxValue; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(float.MaxValue.ToString()); - } - [Test] public async Task TryConvert_NegativeValue_Succeeds() { var converter = new SingleToStringTypeConverter(); - float value = -123.456f; + var value = -123.456f; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvert(value, null, out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo(value.ToString()); } [Test] - public async Task TryConvert_EmptyString_ReturnsFalse() + public async Task TryConvert_SingleToString_Succeeds() { var converter = new SingleToStringTypeConverter(); + var value = 123.456f; - var result = converter.TryConvert(string.Empty, typeof(float), null, out var output); + var result = converter.TryConvert(value, null, out var output); - await Assert.That(result).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(value.ToString()); } [Test] - public async Task TryConvert_StringToSingleWithRounding_RoundsCorrectly() + public async Task TryConvert_WithConversionHint_FormatsCorrectly() { var converter = new SingleToStringTypeConverter(); + var value = 42.5f; - var result = converter.TryConvert("123.456789", typeof(float), 2, out var output); + var result = converter.TryConvert(value, 2, out var output); await Assert.That(result).IsTrue(); - await Assert.That((float)output!).IsEqualTo(123.46f).Within(0.01f); + await Assert.That(output).IsEqualTo("42.50"); } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringConverterTests.cs index e42fceffb3..5e7f2452a8 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringConverterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringConverterTests.cs @@ -8,127 +8,88 @@ namespace ReactiveUI.Tests.Bindings.TypeConverters; public class StringConverterTests { [Test] - public async Task GetAffinityForObjects_ToStringType_Returns2() + public async Task FromType_ReturnsStringType() { var converter = new StringConverter(); - var affinity = converter.GetAffinityForObjects(typeof(int), typeof(string)); - await Assert.That(affinity).IsEqualTo(2); + await Assert.That(converter.FromType).IsEqualTo(typeof(string)); } [Test] - public async Task GetAffinityForObjects_FromStringType_Returns2() + public async Task GetAffinityForObjects_Returns2() { var converter = new StringConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(string)); + var affinity = converter.GetAffinityForObjects(); await Assert.That(affinity).IsEqualTo(2); } [Test] - public async Task GetAffinityForObjects_NotStringType_Returns0() + public async Task ToType_ReturnsStringType() { var converter = new StringConverter(); - var affinity = converter.GetAffinityForObjects(typeof(string), typeof(int)); - await Assert.That(affinity).IsEqualTo(0); + await Assert.That(converter.ToType).IsEqualTo(typeof(string)); } [Test] - public async Task TryConvert_IntToString_Succeeds() + public async Task TryConvertTyped_EmptyString_Succeeds() { var converter = new StringConverter(); - var value = 123; + var value = string.Empty; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvertTyped(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("123"); + await Assert.That(output).IsEqualTo(string.Empty); } [Test] - public async Task TryConvert_StringToString_Succeeds() + public async Task TryConvertTyped_IgnoresConversionHint() { var converter = new StringConverter(); var value = "test"; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvertTyped(value, "some hint", out var output); await Assert.That(result).IsTrue(); await Assert.That(output).IsEqualTo("test"); } [Test] - public async Task TryConvert_NullValue_ReturnsTrue() - { - var converter = new StringConverter(); - - var result = converter.TryConvert(null, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsNull(); - } - - [Test] - public async Task TryConvert_ObjectToString_Succeeds() - { - var converter = new StringConverter(); - var value = new TestObject { Value = "test" }; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("TestObject: test"); - } - - [Test] - public async Task TryConvert_DoubleToString_Succeeds() + public async Task TryConvertTyped_NonStringValue_ReturnsFalse() { var converter = new StringConverter(); - var value = 123.456; - - var result = converter.TryConvert(value, typeof(string), null, out var output); - - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(value.ToString()); - } - - [Test] - public async Task TryConvert_BoolToString_Succeeds() - { - var converter = new StringConverter(); - var value = true; + var value = 123; - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvertTyped(value, null, out var output); - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("True"); + await Assert.That(result).IsFalse(); } [Test] - public async Task TryConvert_DateTimeToString_Succeeds() + public async Task TryConvertTyped_NullValue_ReturnsFalseAndNull() { var converter = new StringConverter(); - var value = new DateTime(2025, 1, 1, 12, 0, 0); - var result = converter.TryConvert(value, typeof(string), null, out var output); + var result = converter.TryConvertTyped(null, null, out var output); - await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo(value.ToString()); + await Assert.That(result).IsFalse(); + await Assert.That(output).IsNull(); } [Test] - public async Task TryConvert_IgnoresConversionHint() + public async Task TryConvertTyped_StringToString_Succeeds() { var converter = new StringConverter(); - var value = 123; + var value = "test"; - var result = converter.TryConvert(value, typeof(string), "some hint", out var output); + var result = converter.TryConvertTyped(value, null, out var output); await Assert.That(result).IsTrue(); - await Assert.That(output).IsEqualTo("123"); + await Assert.That(output).IsEqualTo("test"); } private class TestObject { - public string Value { get; set; } = string.Empty; + public string Value { get; } = string.Empty; public override string ToString() => $"TestObject: {Value}"; } diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToByteTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToByteTypeConverterTests.cs new file mode 100644 index 0000000000..5bb7877e2f --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToByteTypeConverterTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to bytes. +/// +public class StringToByteTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToByteTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsFalse() + { + var converter = new StringToByteTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToByteTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_NegativeValue_ReturnsFalse() + { + var converter = new StringToByteTypeConverter(); + + var result = converter.TryConvert("-1", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + { + var converter = new StringToByteTypeConverter(); + + var result = converter.TryConvert("999", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToByte_Succeeds() + { + var converter = new StringToByteTypeConverter(); + + var result = converter.TryConvert("123", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo((byte)123); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToDecimalTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToDecimalTypeConverterTests.cs new file mode 100644 index 0000000000..934424cb96 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToDecimalTypeConverterTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to decimals. +/// +public class StringToDecimalTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToDecimalTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsFalse() + { + var converter = new StringToDecimalTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToDecimalTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToDecimal_Succeeds() + { + var converter = new StringToDecimalTypeConverter(); + + var result = converter.TryConvert("123.456", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(123.456m); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToDoubleTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToDoubleTypeConverterTests.cs new file mode 100644 index 0000000000..8f6917ea23 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToDoubleTypeConverterTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to doubles. +/// +public class StringToDoubleTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToDoubleTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsFalse() + { + var converter = new StringToDoubleTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToDoubleTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_ScientificNotation_Succeeds() + { + var converter = new StringToDoubleTypeConverter(); + + var result = converter.TryConvert("1.23E+10", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(1.23E+10); + } + + [Test] + public async Task TryConvert_StringToDouble_Succeeds() + { + var converter = new StringToDoubleTypeConverter(); + + var result = converter.TryConvert("123.456", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(123.456); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToIntegerTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToIntegerTypeConverterTests.cs new file mode 100644 index 0000000000..48e066706a --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToIntegerTypeConverterTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to integers. +/// +public class StringToIntegerTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToIntegerTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsFalse() + { + var converter = new StringToIntegerTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToIntegerTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + { + var converter = new StringToIntegerTypeConverter(); + + var result = converter.TryConvert("9999999999", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToInt_Succeeds() + { + var converter = new StringToIntegerTypeConverter(); + + var result = converter.TryConvert("123456", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(123456); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToLongTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToLongTypeConverterTests.cs new file mode 100644 index 0000000000..2da5a444e9 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToLongTypeConverterTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to long integers. +/// +public class StringToLongTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToLongTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsFalse() + { + var converter = new StringToLongTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToLongTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + { + var converter = new StringToLongTypeConverter(); + + var result = converter.TryConvert("99999999999999999999", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToLong_Succeeds() + { + var converter = new StringToLongTypeConverter(); + + var result = converter.TryConvert("123456789012", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(123456789012L); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableByteTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableByteTypeConverterTests.cs new file mode 100644 index 0000000000..fe1738d73e --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableByteTypeConverterTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to nullable bytes. +/// +public class StringToNullableByteTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToNullableByteTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsTrue() + { + var converter = new StringToNullableByteTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsTrue(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToNullableByteTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_NegativeValue_ReturnsFalse() + { + var converter = new StringToNullableByteTypeConverter(); + + var result = converter.TryConvert("-1", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + { + var converter = new StringToNullableByteTypeConverter(); + + var result = converter.TryConvert("999", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToByteNullable_Succeeds() + { + var converter = new StringToNullableByteTypeConverter(); + + var result = converter.TryConvert("123", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo((byte)123); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableDecimalTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableDecimalTypeConverterTests.cs new file mode 100644 index 0000000000..7390a8f450 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableDecimalTypeConverterTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to nullable decimals. +/// +public class StringToNullableDecimalTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToNullableDecimalTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsTrue() + { + var converter = new StringToNullableDecimalTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsTrue(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToNullableDecimalTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToDecimalNullable_Succeeds() + { + var converter = new StringToNullableDecimalTypeConverter(); + + var result = converter.TryConvert("123.456", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(123.456m); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableDoubleTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableDoubleTypeConverterTests.cs new file mode 100644 index 0000000000..8bac1175f5 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableDoubleTypeConverterTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to nullable doubles. +/// +public class StringToNullableDoubleTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToNullableDoubleTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsTrue() + { + var converter = new StringToNullableDoubleTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsTrue(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToNullableDoubleTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_ScientificNotation_Succeeds() + { + var converter = new StringToNullableDoubleTypeConverter(); + + var result = converter.TryConvert("1.23E+10", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(1.23E+10); + } + + [Test] + public async Task TryConvert_StringToDoubleNullable_Succeeds() + { + var converter = new StringToNullableDoubleTypeConverter(); + + var result = converter.TryConvert("123.456", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(123.456); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableIntegerTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableIntegerTypeConverterTests.cs new file mode 100644 index 0000000000..81a775d182 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableIntegerTypeConverterTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to nullable integers. +/// +public class StringToNullableIntegerTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToNullableIntegerTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsTrue() + { + var converter = new StringToNullableIntegerTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsTrue(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToNullableIntegerTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + { + var converter = new StringToNullableIntegerTypeConverter(); + + var result = converter.TryConvert("9999999999", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToIntNullable_Succeeds() + { + var converter = new StringToNullableIntegerTypeConverter(); + + var result = converter.TryConvert("123456", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(123456); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableLongTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableLongTypeConverterTests.cs new file mode 100644 index 0000000000..0a897d947f --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableLongTypeConverterTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to nullable long integers. +/// +public class StringToNullableLongTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToNullableLongTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsTrue() + { + var converter = new StringToNullableLongTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsTrue(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToNullableLongTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + { + var converter = new StringToNullableLongTypeConverter(); + + var result = converter.TryConvert("99999999999999999999", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToLongNullable_Succeeds() + { + var converter = new StringToNullableLongTypeConverter(); + + var result = converter.TryConvert("123456789012", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(123456789012L); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableShortTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableShortTypeConverterTests.cs new file mode 100644 index 0000000000..9863afe325 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableShortTypeConverterTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to nullable short integers. +/// +public class StringToNullableShortTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToNullableShortTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsTrue() + { + var converter = new StringToNullableShortTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsTrue(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToNullableShortTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + { + var converter = new StringToNullableShortTypeConverter(); + + var result = converter.TryConvert("99999", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToShortNullable_Succeeds() + { + var converter = new StringToNullableShortTypeConverter(); + + var result = converter.TryConvert("12345", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo((short)12345); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableSingleTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableSingleTypeConverterTests.cs new file mode 100644 index 0000000000..00bf019763 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToNullableSingleTypeConverterTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to nullable floats (single-precision). +/// +public class StringToNullableSingleTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToNullableSingleTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsTrue() + { + var converter = new StringToNullableSingleTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsTrue(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToNullableSingleTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToSingleNullable_Succeeds() + { + var converter = new StringToNullableSingleTypeConverter(); + + var result = converter.TryConvert("123.456", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsNotNull(); + await Assert.That(output!.Value).IsEqualTo(123.456f).Within(0.001f); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToShortTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToShortTypeConverterTests.cs new file mode 100644 index 0000000000..d93dd6749f --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToShortTypeConverterTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to short integers. +/// +public class StringToShortTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToShortTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsFalse() + { + var converter = new StringToShortTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToShortTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_OutOfRangeValue_ReturnsFalse() + { + var converter = new StringToShortTypeConverter(); + + var result = converter.TryConvert("99999", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToShort_Succeeds() + { + var converter = new StringToShortTypeConverter(); + + var result = converter.TryConvert("12345", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo((short)12345); + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToSingleTypeConverterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToSingleTypeConverterTests.cs new file mode 100644 index 0000000000..edea89a145 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/TypeConverters/StringToSingleTypeConverterTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2025 .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.TypeConverters; + +/// +/// Tests for converting strings to floats (single-precision). +/// +public class StringToSingleTypeConverterTests +{ + [Test] + public async Task GetAffinityForObjects_Returns2() + { + var converter = new StringToSingleTypeConverter(); + var affinity = converter.GetAffinityForObjects(); + await Assert.That(affinity).IsEqualTo(2); + } + + [Test] + public async Task TryConvert_EmptyString_ReturnsFalse() + { + var converter = new StringToSingleTypeConverter(); + + var result = converter.TryConvert(string.Empty, null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_InvalidString_ReturnsFalse() + { + var converter = new StringToSingleTypeConverter(); + + var result = converter.TryConvert("invalid", null, out var output); + + await Assert.That(result).IsFalse(); + } + + [Test] + public async Task TryConvert_StringToSingle_Succeeds() + { + var converter = new StringToSingleTypeConverter(); + + var result = converter.TryConvert("123.456", null, out var output); + + await Assert.That(result).IsTrue(); + await Assert.That(output).IsEqualTo(123.456f).Within(0.001f); + } +} diff --git a/src/tests/ReactiveUI.Tests/ChainedComparerTest.cs b/src/tests/ReactiveUI.Tests/ChainedComparerTest.cs index 86aed43606..ceece70726 100644 --- a/src/tests/ReactiveUI.Tests/ChainedComparerTest.cs +++ b/src/tests/ReactiveUI.Tests/ChainedComparerTest.cs @@ -6,14 +6,14 @@ namespace ReactiveUI.Tests; /// -/// Tests for . +/// Tests for . /// public class ChainedComparerTest { /// - /// Tests that Compare returns 0 when both values are null. + /// Tests that Compare returns 0 when both values are null. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task Compare_BothNull_ReturnsZero() { @@ -25,9 +25,30 @@ public async Task Compare_BothNull_ReturnsZero() } /// - /// Tests that Compare uses comparison when parent is null. + /// Tests that Compare chains multiple comparers correctly. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. + [Test] + public async Task Compare_ChainedComparers_WorksCorrectly() + { + var parent = Comparer.Create((x, y) => x.Priority.CompareTo(y.Priority)); + var comparer = new ChainedComparer(parent, (x, y) => x.Value.CompareTo(y.Value)); + + var obj1 = new TestClass { Priority = 1, Value = 10 }; + var obj2 = new TestClass { Priority = 1, Value = 20 }; + var obj3 = new TestClass { Priority = 2, Value = 5 }; + + var result1 = comparer.Compare(obj1, obj2); + var result2 = comparer.Compare(obj1, obj3); + + await Assert.That(result1).IsLessThan(0); + await Assert.That(result2).IsLessThan(0); + } + + /// + /// Tests that Compare uses comparison when parent is null. + /// + /// A representing the asynchronous operation. [Test] public async Task Compare_NoParent_UsesComparison() { @@ -39,9 +60,9 @@ public async Task Compare_NoParent_UsesComparison() } /// - /// Tests that Compare uses parent result when non-zero. + /// Tests that Compare uses parent result when non-zero. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task Compare_ParentReturnsNonZero_UsesParentResult() { @@ -54,9 +75,9 @@ public async Task Compare_ParentReturnsNonZero_UsesParentResult() } /// - /// Tests that Compare uses comparison when parent returns zero. + /// Tests that Compare uses comparison when parent returns zero. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task Compare_ParentReturnsZero_UsesComparison() { @@ -69,28 +90,7 @@ public async Task Compare_ParentReturnsZero_UsesComparison() } /// - /// Tests that Compare chains multiple comparers correctly. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Compare_ChainedComparers_WorksCorrectly() - { - var parent = Comparer.Create((x, y) => x.Priority.CompareTo(y.Priority)); - var comparer = new ChainedComparer(parent, (x, y) => x.Value.CompareTo(y.Value)); - - var obj1 = new TestClass { Priority = 1, Value = 10 }; - var obj2 = new TestClass { Priority = 1, Value = 20 }; - var obj3 = new TestClass { Priority = 2, Value = 5 }; - - var result1 = comparer.Compare(obj1, obj2); - var result2 = comparer.Compare(obj1, obj3); - - await Assert.That(result1).IsLessThan(0); - await Assert.That(result2).IsLessThan(0); - } - - /// - /// Test class for comparison testing. + /// Test class for comparison testing. /// private class TestClass { diff --git a/src/tests/ReactiveUI.Tests/ChangeSetMixinTest.cs b/src/tests/ReactiveUI.Tests/ChangeSetMixinTest.cs index fcf5cfb095..1834bc17a8 100644 --- a/src/tests/ReactiveUI.Tests/ChangeSetMixinTest.cs +++ b/src/tests/ReactiveUI.Tests/ChangeSetMixinTest.cs @@ -8,56 +8,64 @@ namespace ReactiveUI.Tests; /// -/// Tests for . +/// Tests for . /// public class ChangeSetMixinTest { /// - /// Tests that HasCountChanged returns true when adds are present. + /// Tests that CountChanged filters to only count changes. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task HasCountChanged_WithAdds_ReturnsTrue() + public async Task CountChanged_FiltersToOnlyCountChanges() { - var changeSet = new ChangeSet([new Change(ListChangeReason.Add, 1, 0)]); - - var result = changeSet.HasCountChanged(); + var subject = new Subject(); + var results = new List(); - await Assert.That(result).IsTrue(); - } + subject.CountChanged().ObserveOn(ImmediateScheduler.Instance).Subscribe(results.Add); - /// - /// Tests that HasCountChanged returns true when removes are present. - /// - /// A representing the asynchronous operation. - [Test] - public async Task HasCountChanged_WithRemoves_ReturnsTrue() - { - var changeSet = new ChangeSet([new Change(ListChangeReason.Remove, 1, 0)]); + var addChangeSet = new ChangeSet([new Change(ListChangeReason.Add, 1, 0)]); + var updateChangeSet = new ChangeSet([new Change(ListChangeReason.Replace, 2, 1, 0, 0)]); + var removeChangeSet = new ChangeSet([new Change(ListChangeReason.Remove, 1, 0)]); - var result = changeSet.HasCountChanged(); + subject.OnNext(addChangeSet); + subject.OnNext(updateChangeSet); + subject.OnNext(removeChangeSet); - await Assert.That(result).IsTrue(); + await Assert.That(results).Count().IsEqualTo(2); + await Assert.That(results[0]).IsEqualTo(addChangeSet); + await Assert.That(results[1]).IsEqualTo(removeChangeSet); } /// - /// Tests that HasCountChanged returns false when only updates are present. + /// Tests that generic CountChanged filters to only count changes. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task HasCountChanged_WithOnlyUpdates_ReturnsFalse() + public async Task CountChanged_Generic_FiltersToOnlyCountChanges() { - var changeSet = new ChangeSet([new Change(ListChangeReason.Replace, 2, 1, 0, 0)]); + var subject = new Subject>(); + var results = new List>(); - var result = changeSet.HasCountChanged(); + subject.CountChanged().ObserveOn(ImmediateScheduler.Instance).Subscribe(results.Add); - await Assert.That(result).IsFalse(); + var addChangeSet = new ChangeSet([new Change(ListChangeReason.Add, 1, 0)]); + var updateChangeSet = new ChangeSet([new Change(ListChangeReason.Replace, 2, 1, 0, 0)]); + var removeChangeSet = new ChangeSet([new Change(ListChangeReason.Remove, 1, 0)]); + + subject.OnNext(addChangeSet); + subject.OnNext(updateChangeSet); + subject.OnNext(removeChangeSet); + + await Assert.That(results).Count().IsEqualTo(2); + await Assert.That(results[0]).IsEqualTo(addChangeSet); + await Assert.That(results[1]).IsEqualTo(removeChangeSet); } /// - /// Tests that HasCountChanged throws for null changeSet. + /// Tests that HasCountChanged throws for null changeSet. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task HasCountChanged_NullChangeSet_Throws() { @@ -68,52 +76,44 @@ await Assert.That(() => changeSet.HasCountChanged()) } /// - /// Tests that CountChanged filters to only count changes. + /// Tests that HasCountChanged returns true when adds are present. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task CountChanged_FiltersToOnlyCountChanges() + public async Task HasCountChanged_WithAdds_ReturnsTrue() { - var subject = new Subject(); - var results = new List(); - - subject.CountChanged().ObserveOn(ImmediateScheduler.Instance).Subscribe(results.Add); - - var addChangeSet = new ChangeSet([new Change(ListChangeReason.Add, 1, 0)]); - var updateChangeSet = new ChangeSet([new Change(ListChangeReason.Replace, 2, 1, 0, 0)]); - var removeChangeSet = new ChangeSet([new Change(ListChangeReason.Remove, 1, 0)]); + var changeSet = new ChangeSet([new Change(ListChangeReason.Add, 1, 0)]); - subject.OnNext(addChangeSet); - subject.OnNext(updateChangeSet); - subject.OnNext(removeChangeSet); + var result = changeSet.HasCountChanged(); - await Assert.That(results).Count().IsEqualTo(2); - await Assert.That(results[0]).IsEqualTo(addChangeSet); - await Assert.That(results[1]).IsEqualTo(removeChangeSet); + await Assert.That(result).IsTrue(); } /// - /// Tests that generic CountChanged filters to only count changes. + /// Tests that HasCountChanged returns false when only updates are present. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task CountChanged_Generic_FiltersToOnlyCountChanges() + public async Task HasCountChanged_WithOnlyUpdates_ReturnsFalse() { - var subject = new Subject>(); - var results = new List>(); + var changeSet = new ChangeSet([new Change(ListChangeReason.Replace, 2, 1, 0, 0)]); - subject.CountChanged().ObserveOn(ImmediateScheduler.Instance).Subscribe(results.Add); + var result = changeSet.HasCountChanged(); - var addChangeSet = new ChangeSet([new Change(ListChangeReason.Add, 1, 0)]); - var updateChangeSet = new ChangeSet([new Change(ListChangeReason.Replace, 2, 1, 0, 0)]); - var removeChangeSet = new ChangeSet([new Change(ListChangeReason.Remove, 1, 0)]); + await Assert.That(result).IsFalse(); + } - subject.OnNext(addChangeSet); - subject.OnNext(updateChangeSet); - subject.OnNext(removeChangeSet); + /// + /// Tests that HasCountChanged returns true when removes are present. + /// + /// A representing the asynchronous operation. + [Test] + public async Task HasCountChanged_WithRemoves_ReturnsTrue() + { + var changeSet = new ChangeSet([new Change(ListChangeReason.Remove, 1, 0)]); - await Assert.That(results).Count().IsEqualTo(2); - await Assert.That(results[0]).IsEqualTo(addChangeSet); - await Assert.That(results[1]).IsEqualTo(removeChangeSet); + var result = changeSet.HasCountChanged(); + + await Assert.That(result).IsTrue(); } } diff --git a/src/tests/ReactiveUI.Tests/CommandBinding/CommandBindingTests.cs b/src/tests/ReactiveUI.Tests/CommandBinding/CommandBindingTests.cs new file mode 100644 index 0000000000..c4f18767f0 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/CommandBinding/CommandBindingTests.cs @@ -0,0 +1,190 @@ +// Copyright (c) 2025 .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.Input; +using ReactiveUI.Builder; + +namespace ReactiveUI.Tests.CommandBinding; + +/// +/// Tests for command binding. +/// +/// +/// This test fixture is marked as NotInParallel because tests call +/// Locator.CurrentMutable to register ICreatesCommandBinding implementations, +/// which mutate global service locator state. +/// +[NotInParallel] +[TestExecutor] +public class CommandBindingTests +{ + [Test] + public async Task CommandBinderImplementation_Should_Bind_Command_To_Event() + { + var binder = new CommandBinderImplementation(); + var viewModel = new FakeViewModel(); + var view = new FakeView { ViewModel = viewModel }; + + var disp = binder.BindCommand( + viewModel, + view, + vm => vm.Command, + v => v.Control, + Observable.Return((object?)null), + "Click"); + + await Assert.That(disp).IsNotNull(); + + var executed = false; + viewModel.Command.Subscribe(_ => executed = true); + + view.Control.RaiseClick(); + + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task CommandBinderImplementation_Should_Use_Custom_Binder() + { + var binder = new CommandBinderImplementation(); + var viewModel = new FakeViewModel(); + var view = new FakeView { ViewModel = viewModel }; + + // FakeCustomControl has affinity with FakeCustomBinder + var disp = binder.BindCommand( + viewModel, + view, + vm => vm.Command, + v => v.CustomControl, + Observable.Return((object?)null)); + + await Assert.That(disp).IsNotNull(); + await Assert.That(FakeCustomBinder.BindCalled).IsTrue(); + } + + /// + /// Provides test execution support for command binding scenarios using the ReactiveUI framework. + /// + public class CommandBindingExecutorTests : ITestExecutor + { + /// + public async ValueTask ExecuteTest(TestContext context, Func action) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(action); + + try + { + var scheduler = ImmediateScheduler.Instance; + + RxAppBuilder.ResetForTesting(); + RxAppBuilder + .CreateReactiveUIBuilder() + .WithMainThreadScheduler(scheduler) + .WithTaskPoolScheduler(scheduler) + .WithRegistration(r => r.RegisterConstant(new CreatesCommandBindingViaEvent())) + .WithRegistration(r => r.RegisterConstant(new FakeCustomBinder())) + .WithCoreServices() + .BuildApp(); + + RxSchedulers.MainThreadScheduler = scheduler; + RxSchedulers.TaskpoolScheduler = scheduler; + + await action(); + } + finally + { + RxAppBuilder.ResetForTesting(); + } + } + } + + private class FakeControl + { + public event EventHandler? Click; + + public void RaiseClick() => Click?.Invoke(this, EventArgs.Empty); + } + + private class FakeCustomBinder : ICreatesCommandBinding + { + public FakeCustomBinder() => BindCalled = false; + + public static bool BindCalled { get; set; } + + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject(ICommand? command, T? target, IObservable commandParameter) + where T : class + { + BindCalled = true; + return Disposable.Empty; + } + + [RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public IDisposable? BindCommandToObject( + ICommand? command, + T? target, + IObservable commandParameter, + string eventName) + where T : class + { + BindCalled = true; + return Disposable.Empty; + } + + public IDisposable? BindCommandToObject( + ICommand? command, + T? target, + IObservable commandParameter, + Action> addHandler, + Action> removeHandler) + where T : class + where TEventArgs : EventArgs + { + BindCalled = true; + return Disposable.Empty; + } + + public int GetAffinityForObject(bool hasEventTarget) + { + if (typeof(T) == typeof(FakeCustomControl)) + { + return 100; // High affinity + } + + return 0; + } + } + + private class FakeCustomControl + { + } + + private class FakeView : ReactiveObject, IViewFor + { + private FakeViewModel? _viewModel; + + public FakeControl Control { get; } = new(); + + public FakeCustomControl CustomControl { get; } = new(); + + public FakeViewModel? ViewModel + { + get => _viewModel; + set => this.RaiseAndSetIfChanged(ref _viewModel, value); + } + + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (FakeViewModel?)value; + } + } + + private class FakeViewModel : ReactiveObject + { + public ReactiveCommand Command { get; } = ReactiveCommand.Create(() => { }); + } +} diff --git a/src/tests/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs b/src/tests/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs index 736590a0cd..56cff78f77 100644 --- a/src/tests/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs +++ b/src/tests/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs @@ -5,26 +5,27 @@ using DynamicData; -using Microsoft.Reactive.Testing; - -using ReactiveUI.Testing; - -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Commands; /// -/// Tests for the ReactiveCommand Combined functionality. +/// Tests for the ReactiveCommand Combined functionality. /// public class CombinedReactiveCommandTest { /// - /// Tests that determines whether this instance [can execute is false if any child cannot execute]. + /// Tests that determines whether this instance [can execute is false if any child cannot execute]. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task CanExecuteIsFalseIfAnyChildCannotExecute() { - var child1 = ReactiveCommand.Create(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); - var child2 = ReactiveCommand.Create(static () => Observables.Unit, Observables.False, ImmediateScheduler.Instance); + var child1 = ReactiveCommand.Create( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); + var child2 = ReactiveCommand.Create( + static () => Observables.Unit, + Observables.False, + ImmediateScheduler.Instance); var childCommands = new[] { child1, child2 }; var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: ImmediateScheduler.Instance); fixture.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); @@ -34,14 +35,18 @@ public async Task CanExecuteIsFalseIfAnyChildCannotExecute() } /// - /// Test that determines whether this instance [can execute is false if parent can execute is false]. + /// Test that determines whether this instance [can execute is false if parent can execute is false]. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task CanExecuteIsFalseIfParentCanExecuteIsFalse() { - var child1 = ReactiveCommand.Create(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); - var child2 = ReactiveCommand.Create(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); + var child1 = ReactiveCommand.Create( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); + var child2 = ReactiveCommand.Create( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); var childCommands = new[] { child1, child2 }; var fixture = ReactiveCommand.CreateCombined(childCommands, Observables.False, ImmediateScheduler.Instance); fixture.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); @@ -51,18 +56,25 @@ public async Task CanExecuteIsFalseIfParentCanExecuteIsFalse() } /// - /// Test that determines whether this instance [can execute ticks failures in child can execute through thrown exceptions]. + /// Test that determines whether this instance [can execute ticks failures in child can execute through thrown + /// exceptions]. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task CanExecuteTicksFailuresInChildCanExecuteThroughThrownExceptions() { var canExecuteSubject = new Subject(); - var child1 = ReactiveCommand.Create(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); - var child2 = ReactiveCommand.Create(static () => Observables.Unit, canExecuteSubject, ImmediateScheduler.Instance); + var child1 = ReactiveCommand.Create( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); + var child2 = ReactiveCommand.Create( + static () => Observables.Unit, + canExecuteSubject, + ImmediateScheduler.Instance); var childCommands = new[] { child1, child2 }; var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: ImmediateScheduler.Instance); - fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions).Subscribe(); + fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions) + .Subscribe(); canExecuteSubject.OnError(new InvalidOperationException("oops")); @@ -71,18 +83,23 @@ public async Task CanExecuteTicksFailuresInChildCanExecuteThroughThrownException } /// - /// Test that determines whether this instance [can execute ticks failures through thrown exceptions]. + /// Test that determines whether this instance [can execute ticks failures through thrown exceptions]. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task CanExecuteTicksFailuresThroughThrownExceptions() { var canExecuteSubject = new Subject(); - var child1 = ReactiveCommand.Create(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); - var child2 = ReactiveCommand.Create(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); + var child1 = ReactiveCommand.Create( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); + var child2 = ReactiveCommand.Create( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); var childCommands = new[] { child1, child2 }; var fixture = ReactiveCommand.CreateCombined(childCommands, canExecuteSubject, ImmediateScheduler.Instance); - fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions).Subscribe(); + fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions) + .Subscribe(); canExecuteSubject.OnError(new InvalidOperationException("oops")); @@ -91,42 +108,52 @@ public async Task CanExecuteTicksFailuresThroughThrownExceptions() } /// - /// A test that checks that all the exceptions that were delivered through the output scheduler. + /// A test that checks that all the exceptions that were delivered through the output scheduler. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ExceptionsAreDeliveredOnOutputScheduler() => - await new TestScheduler().With( - async scheduler => - { - var child = ReactiveCommand.CreateFromObservable(() => Observable.Throw(new InvalidOperationException("oops"))); - var childCommands = new[] { child }; - var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: scheduler); - Exception? exception = null; - fixture.ThrownExceptions.Subscribe(ex => exception = ex); - fixture.Execute().Subscribe(_ => { }, _ => { }); - await Assert.That(exception).IsNull(); - scheduler.Start(); - await Assert.That(exception).IsTypeOf(); - }); + [TestExecutor] + public async Task ExceptionsAreDeliveredOnOutputScheduler() + { + var scheduler = TestContext.Current!.GetScheduler(); + var child = ReactiveCommand.CreateFromObservable(() => + Observable.Throw(new InvalidOperationException("oops"))); + var childCommands = new[] { child }; + var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: scheduler); + Exception? exception = null; + fixture.ThrownExceptions.Subscribe(ex => exception = ex); + fixture.Execute().Subscribe(_ => { }, _ => { }); + + // With ImmediateScheduler, exceptions are delivered immediately + await Assert.That(exception).IsTypeOf(); + } /// - /// A test that executes the executes all child commands. + /// A test that executes the executes all child commands. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ExecuteExecutesAllChildCommands() { - var child1 = ReactiveCommand.Create(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); - var child2 = ReactiveCommand.Create(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); - var child3 = ReactiveCommand.Create(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); + var child1 = ReactiveCommand.Create( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); + var child2 = ReactiveCommand.Create( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); + var child3 = ReactiveCommand.Create( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); var childCommands = new[] { child1, child2, child3 }; var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: ImmediateScheduler.Instance); fixture.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var isExecuting).Subscribe(); - child1.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var child1IsExecuting).Subscribe(); - child2.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var child2IsExecuting).Subscribe(); - child3.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var child3IsExecuting).Subscribe(); + child1.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var child1IsExecuting) + .Subscribe(); + child2.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var child2IsExecuting) + .Subscribe(); + child3.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var child3IsExecuting) + .Subscribe(); fixture.Execute().Subscribe(); @@ -167,17 +194,22 @@ public async Task ExecuteExecutesAllChildCommands() } /// - /// Test that executes the ticks errors in any child command through thrown exceptions. + /// Test that executes the ticks errors in any child command through thrown exceptions. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ExecuteTicksErrorsInAnyChildCommandThroughThrownExceptions() { - var child1 = ReactiveCommand.CreateFromObservable(static () => Observables.Unit, outputScheduler: ImmediateScheduler.Instance); - var child2 = ReactiveCommand.CreateFromObservable(static () => Observable.Throw(new InvalidOperationException("oops")), outputScheduler: ImmediateScheduler.Instance); + var child1 = ReactiveCommand.CreateFromObservable( + static () => Observables.Unit, + outputScheduler: ImmediateScheduler.Instance); + var child2 = ReactiveCommand.CreateFromObservable( + static () => Observable.Throw(new InvalidOperationException("oops")), + outputScheduler: ImmediateScheduler.Instance); var childCommands = new[] { child1, child2 }; var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: ImmediateScheduler.Instance); - fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions).Subscribe(); + fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var thrownExceptions) + .Subscribe(); fixture.Execute().Subscribe(static _ => { }, static _ => { }); @@ -186,14 +218,18 @@ public async Task ExecuteTicksErrorsInAnyChildCommandThroughThrownExceptions() } /// - /// Test that executes the ticks through the results. + /// Test that executes the ticks through the results. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ExecuteTicksThroughTheResults() { - var child1 = ReactiveCommand.CreateFromObservable(static () => Observable.Return(1), outputScheduler: ImmediateScheduler.Instance); - var child2 = ReactiveCommand.CreateFromObservable(static () => Observable.Return(2), outputScheduler: ImmediateScheduler.Instance); + var child1 = ReactiveCommand.CreateFromObservable( + static () => Observable.Return(1), + outputScheduler: ImmediateScheduler.Instance); + var child2 = ReactiveCommand.CreateFromObservable( + static () => Observable.Return(2), + outputScheduler: ImmediateScheduler.Instance); var childCommands = new[] { child1, child2 }; var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: ImmediateScheduler.Instance); @@ -211,26 +247,23 @@ public async Task ExecuteTicksThroughTheResults() } /// - /// Test that checks that results is ticked through specified scheduler. + /// Test that checks that results is ticked through specified scheduler. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ResultIsTickedThroughSpecifiedScheduler() => - await new TestScheduler().WithAsync( -static async scheduler => - { - // Allow scheduler to run freely - var child1 = ReactiveCommand.Create(static () => Observable.Return(1)); - var child2 = ReactiveCommand.CreateRunInBackground(static () => Observable.Return(2)); - var childCommands = new[] { child1, child2 }; - var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: scheduler); - fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); - - fixture.Execute().Subscribe(); - await Assert.That(results).IsEmpty(); - - scheduler.AdvanceByMs(1); - await Assert.That(results).Count().IsEqualTo(1); - return Task.CompletedTask; - }); + [TestExecutor] + public async Task ResultIsTickedThroughSpecifiedScheduler() + { + var scheduler = TestContext.Current!.GetScheduler(); + var child1 = ReactiveCommand.Create(static () => Observable.Return(1)); + var child2 = ReactiveCommand.CreateRunInBackground(static () => Observable.Return(2)); + var childCommands = new[] { child1, child2 }; + var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: scheduler); + fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).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 cadb9d145e..18c265dfb4 100644 --- a/src/tests/ReactiveUI.Tests/Commands/CreatesCommandBindingTests.cs +++ b/src/tests/ReactiveUI.Tests/Commands/CreatesCommandBindingTests.cs @@ -3,14 +3,16 @@ // 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; +using ReactiveUI.Tests.ReactiveObjects.Mocks; + +namespace ReactiveUI.Tests.Commands; public class CreatesCommandBindingTests { /// - /// Test that makes sure events binder binds to explicit event. + /// Test that makes sure events binder binds to explicit event. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task EventBinderBindsToExplicitEvent() { @@ -21,11 +23,15 @@ public async Task EventBinderBindsToExplicitEvent() using (Assert.Multiple()) { - await Assert.That(fixture.GetAffinityForObject(input.GetType(), true)).IsGreaterThan(0); - await Assert.That(fixture.GetAffinityForObject(input.GetType(), false)).IsLessThanOrEqualTo(0); + await Assert.That(fixture.GetAffinityForObject(true)).IsGreaterThan(0); + await Assert.That(fixture.GetAffinityForObject(false)).IsLessThanOrEqualTo(0); } - var disposable = fixture.BindCommandToObject(cmd, input, Observable.Return((object)5), "PropertyChanged"); + var disposable = fixture.BindCommandToObject( + cmd, + input, + Observable.Return((object)5), + "PropertyChanged"); input.IsNotNullString = "Foo"; await Assert.That(wasCalled).IsTrue(); diff --git a/src/tests/ReactiveUI.Tests/Commands/Mocks/FakeCommand.cs b/src/tests/ReactiveUI.Tests/Commands/Mocks/FakeCommand.cs index 11e7bf1824..7a6eca3e68 100644 --- a/src/tests/ReactiveUI.Tests/Commands/Mocks/FakeCommand.cs +++ b/src/tests/ReactiveUI.Tests/Commands/Mocks/FakeCommand.cs @@ -5,15 +5,15 @@ using System.Windows.Input; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Commands.Mocks; /// -/// A fake command that can be executed as part of a test. +/// A fake command that can be executed as part of a test. /// public class FakeCommand : ICommand { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public FakeCommand() { @@ -22,26 +22,29 @@ public FakeCommand() } /// - /// Occurs when changes occur that affect whether or not the command should execute. + /// Occurs when changes occur that affect whether or not the command should execute. /// public event EventHandler? CanExecuteChanged; /// - /// Gets the can execute parameter. + /// Gets the can execute parameter. /// public object? CanExecuteParameter { get; private set; } /// - /// Gets the execute parameter. + /// Gets the execute parameter. /// public object? ExecuteParameter { get; private set; } /// - /// Defines the method that determines whether the command can execute in its current state. + /// Defines the method that determines whether the command can execute in its current state. /// - /// Data used by the command. If the command does not require data to be passed, this object can be set to . + /// + /// Data used by the command. If the command does not require data to be passed, this object can + /// be set to . + /// /// - /// if this command can be executed; otherwise, . + /// if this command can be executed; otherwise, . /// public bool CanExecute(object? parameter) { @@ -50,14 +53,17 @@ public bool CanExecute(object? parameter) } /// - /// Defines the method to be called when the command is invoked. + /// Defines the method to be called when the command is invoked. /// - /// Data used by the command. If the command does not require data to be passed, this object can be set to . + /// + /// Data used by the command. If the command does not require data to be passed, this object can + /// be set to . + /// public void Execute(object? parameter) => ExecuteParameter = parameter; /// - /// Notifies the can execute changed. + /// Notifies the can execute changed. /// - /// The instance containing the event data. + /// The instance containing the event data. protected virtual void NotifyCanExecuteChanged(EventArgs e) => CanExecuteChanged?.Invoke(this, e); } diff --git a/src/tests/ReactiveUI.Tests/Commands/Mocks/ICommandHolder.cs b/src/tests/ReactiveUI.Tests/Commands/Mocks/ICommandHolder.cs index 0ddf55eda9..97ccfbbad8 100644 --- a/src/tests/ReactiveUI.Tests/Commands/Mocks/ICommandHolder.cs +++ b/src/tests/ReactiveUI.Tests/Commands/Mocks/ICommandHolder.cs @@ -5,17 +5,17 @@ using System.Windows.Input; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Commands.Mocks; /// -/// A ReactiveObject which hosts a command. +/// A ReactiveObject which hosts a command. /// public class ICommandHolder : ReactiveObject { private ICommand? _theCommand; /// - /// Gets or sets the command. + /// Gets or sets the command. /// public ICommand? TheCommand { diff --git a/src/tests/ReactiveUI.Tests/Commands/Mocks/ReactiveCommandHolder.cs b/src/tests/ReactiveUI.Tests/Commands/Mocks/ReactiveCommandHolder.cs index 979b2b9c36..da3b704826 100644 --- a/src/tests/ReactiveUI.Tests/Commands/Mocks/ReactiveCommandHolder.cs +++ b/src/tests/ReactiveUI.Tests/Commands/Mocks/ReactiveCommandHolder.cs @@ -3,10 +3,10 @@ // 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; +namespace ReactiveUI.Tests.Commands.Mocks; /// -/// A ReactiveObject which hosts a ReactiveCommand. +/// A ReactiveObject which hosts a ReactiveCommand. /// /// public class ReactiveCommandHolder : ReactiveObject @@ -14,7 +14,7 @@ public class ReactiveCommandHolder : ReactiveObject private ReactiveCommand? _theCommand; /// - /// Gets or sets the command. + /// Gets or sets the command. /// public ReactiveCommand? TheCommand { diff --git a/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs new file mode 100644 index 0000000000..ce44c97d75 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs @@ -0,0 +1,1790 @@ +// Copyright (c) 2025 .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.Input; +using DynamicData; +using ReactiveUI.Tests.Commands.Mocks; +using ReactiveUI.Tests.Mocks; + +namespace ReactiveUI.Tests.Commands; + +/// +/// Comprehensive test suite for ReactiveCommand. +/// Tests cover all factory methods, behaviors, and edge cases. +/// Organized into logical test groups for maintainability. +/// +[NotInParallel] +[TestExecutor] +public class ReactiveCommandTest +{ + [Test] + public async Task CanExecute_IsBehavioral() + { + var command = ReactiveCommand.Create( + () => { }, + outputScheduler: ImmediateScheduler.Instance); + command.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); + + using (Assert.Multiple()) + { + await Assert.That(canExecute).Count().IsEqualTo(1); + await Assert.That(canExecute[0]).IsTrue(); + } + } + + [Test] + + [TestExecutor] + public async Task CanExecute_IsFalseWhileExecuting() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + var execute = Observables.Unit.Delay(TimeSpan.FromSeconds(1), scheduler); + var command = ReactiveCommand.CreateFromObservable( + () => execute, + outputScheduler: scheduler); + command.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); + + command.Execute().Subscribe(); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + using (Assert.Multiple()) + { + await Assert.That(canExecute).Count().IsEqualTo(2); + await Assert.That(canExecute[0]).IsTrue(); + await Assert.That(canExecute[1]).IsFalse(); + } + } + + [Test] + public async Task CanExecute_OnlyTicksDistinctValues() + { + var canExecuteSubject = new BehaviorSubject(false); + var command = ReactiveCommand.Create( + () => { }, + canExecuteSubject, + ImmediateScheduler.Instance); + command.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); + + canExecuteSubject.OnNext(false); + canExecuteSubject.OnNext(false); + canExecuteSubject.OnNext(true); + canExecuteSubject.OnNext(true); + + using (Assert.Multiple()) + { + await Assert.That(canExecute).Count().IsEqualTo(2); + await Assert.That(canExecute[0]).IsFalse(); + await Assert.That(canExecute[1]).IsTrue(); + } + } + + [Test] + public async Task CanExecute_RespectsProvidedObservable() + { + var canExecuteSubject = new BehaviorSubject(false); + var command = ReactiveCommand.Create( + () => { }, + canExecuteSubject, + ImmediateScheduler.Instance); + command.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); + + canExecuteSubject.OnNext(true); + canExecuteSubject.OnNext(false); + + using (Assert.Multiple()) + { + await Assert.That(canExecute).Count().IsEqualTo(3); + await Assert.That(canExecute[0]).IsFalse(); + await Assert.That(canExecute[1]).IsTrue(); + await Assert.That(canExecute[2]).IsFalse(); + } + } + + [Test] + public async Task CanExecute_TicksExceptionsThroughThrownExceptions() + { + var canExecuteSubject = new Subject(); + var command = ReactiveCommand.Create( + () => { }, + canExecuteSubject, + ImmediateScheduler.Instance); + command.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var exceptions) + .Subscribe(); + + canExecuteSubject.OnError(new InvalidOperationException("Test error")); + + await Assert.That(exceptions).Count().IsEqualTo(1); + await Assert.That(exceptions[0]).IsTypeOf(); + } + + [Test] + public async Task CanExecute_UnsubscribesOnDisposal() + { + var canExecuteSubject = new BehaviorSubject(true); + var command = ReactiveCommand.Create( + () => { }, + canExecuteSubject, + ImmediateScheduler.Instance); + + await Assert.That(canExecuteSubject.HasObservers).IsTrue(); + + command.Dispose(); + + await Assert.That(canExecuteSubject.HasObservers).IsFalse(); + } + + [Test] + public async Task Create_Action_ExecutesSuccessfully() + { + var executed = false; + var command = ReactiveCommand.Create( + () => executed = true, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task Create_Action_RespectsCanExecute() + { + var canExecute = new BehaviorSubject(false); + var executed = false; + var command = ReactiveCommand.Create( + () => executed = true, + canExecute, + ImmediateScheduler.Instance); + var source = new Subject(); + + source.InvokeCommand(command); + + source.OnNext(Unit.Default); + await Assert.That(executed).IsFalse(); + + canExecute.OnNext(true); + source.OnNext(Unit.Default); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task Create_Action_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.Create(null!); + await Task.CompletedTask; + }); + + [Test] + public async Task Create_ActionWithParam_HandlesMultipleExecutions() + { + var parameters = new List(); + var command = ReactiveCommand.Create( + param => parameters.Add(param), + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(1); + await command.Execute(2); + await command.Execute(3); + + using (Assert.Multiple()) + { + await Assert.That(parameters).Count().IsEqualTo(3); + await Assert.That(parameters[0]).IsEqualTo(1); + await Assert.That(parameters[1]).IsEqualTo(2); + await Assert.That(parameters[2]).IsEqualTo(3); + } + } + + [Test] + public async Task Create_ActionWithParam_PassesParameterCorrectly() + { + var receivedParam = 0; + var command = ReactiveCommand.Create( + param => receivedParam = param, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(42); + await Assert.That(receivedParam).IsEqualTo(42); + } + + [Test] + public async Task Create_ActionWithParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.Create((Action)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task Create_Func_ReturnsResult() + { + var command = ReactiveCommand.Create( + () => 42, + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(); + + await Assert.That(results).Count().IsEqualTo(1); + await Assert.That(results[0]).IsEqualTo(42); + } + + [Test] + public async Task Create_Func_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.Create((Func)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task Create_Func_TicksMultipleResults() + { + var counter = 0; + var command = ReactiveCommand.Create( + () => ++counter, + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(); + await command.Execute(); + await command.Execute(); + + using (Assert.Multiple()) + { + await Assert.That(results).Count().IsEqualTo(3); + await Assert.That(results[0]).IsEqualTo(1); + await Assert.That(results[1]).IsEqualTo(2); + await Assert.That(results[2]).IsEqualTo(3); + } + } + + [Test] + public async Task Create_FuncWithParam_ReturnsResultFromParameter() + { + var command = ReactiveCommand.Create( + param => param.ToString(), + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(42); + + await Assert.That(results[0]).IsEqualTo("42"); + } + + [Test] + public async Task Create_FuncWithParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.Create((Func)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task Create_FuncWithParam_TransformsParameters() + { + var command = ReactiveCommand.Create( + param => param * 2, + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(5); + await command.Execute(10); + await command.Execute(15); + + using (Assert.Multiple()) + { + await Assert.That(results).Count().IsEqualTo(3); + await Assert.That(results[0]).IsEqualTo(10); + await Assert.That(results[1]).IsEqualTo(20); + await Assert.That(results[2]).IsEqualTo(30); + } + } + + [Test] + public async Task CreateCombined_CanExecuteIsFalseIfAnyChildCannotExecute() + { + var canExecute1 = new BehaviorSubject(true); + var canExecute2 = new BehaviorSubject(false); + + var cmd1 = ReactiveCommand.Create( + x => x, + canExecute1, + ImmediateScheduler.Instance); + var cmd2 = ReactiveCommand.Create( + x => x, + canExecute2, + ImmediateScheduler.Instance); + + var combined = ReactiveCommand.CreateCombined( + [cmd1, cmd2], + outputScheduler: ImmediateScheduler.Instance); + + var canExecuteValue = await combined.CanExecute.FirstAsync(); + await Assert.That(canExecuteValue).IsFalse(); + + canExecute2.OnNext(true); + canExecuteValue = await combined.CanExecute.FirstAsync(); + await Assert.That(canExecuteValue).IsTrue(); + } + + [Test] + public async Task CreateCombined_ExecutesAllChildCommands() + { + var executed1 = false; + var executed2 = false; + var executed3 = false; + + var cmd1 = ReactiveCommand.Create( + x => + { + executed1 = true; + return x * 2; + }, + outputScheduler: ImmediateScheduler.Instance); + var cmd2 = ReactiveCommand.Create( + x => + { + executed2 = true; + return x * 3; + }, + outputScheduler: ImmediateScheduler.Instance); + var cmd3 = ReactiveCommand.Create( + x => + { + executed3 = true; + return x * 4; + }, + outputScheduler: ImmediateScheduler.Instance); + + var combined = ReactiveCommand.CreateCombined( + [cmd1, cmd2, cmd3], + outputScheduler: ImmediateScheduler.Instance); + + combined.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await combined.Execute(5); + + using (Assert.Multiple()) + { + await Assert.That(executed1).IsTrue(); + await Assert.That(executed2).IsTrue(); + await Assert.That(executed3).IsTrue(); + await Assert.That(results).Count().IsEqualTo(1); + await Assert.That(results[0]).Count().IsEqualTo(3); + await Assert.That(results[0][0]).IsEqualTo(10); + await Assert.That(results[0][1]).IsEqualTo(15); + await Assert.That(results[0][2]).IsEqualTo(20); + } + } + + [Test] + public async Task CreateCombined_PropagatesChildExceptions() + { + var cmd1 = ReactiveCommand.Create( + x => x, + outputScheduler: ImmediateScheduler.Instance); + var cmd2 = ReactiveCommand.Create( + x => throw new InvalidOperationException("Test exception"), + outputScheduler: ImmediateScheduler.Instance); + + var combined = ReactiveCommand.CreateCombined( + [cmd1, cmd2], + outputScheduler: ImmediateScheduler.Instance); + + combined.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var exceptions) + .Subscribe(); + + combined.Execute(5).Subscribe(_ => { }, _ => { }); + + await Assert.That(exceptions).Count().IsEqualTo(1); + await Assert.That(exceptions[0]).IsTypeOf(); + } + + [Test] + public async Task CreateCombined_ThrowsOnEmptyChildCommands() => + await Assert.ThrowsAsync(async () => + { + ReactiveCommand.CreateCombined([]); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateCombined_ThrowsOnNullChildCommands() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateCombined(null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromObservable_WithoutParam_EmitsMultipleValues() + { + var command = ReactiveCommand.CreateFromObservable( + () => new[] { 1, 2, 3 }.ToObservable(), + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(); + + using (Assert.Multiple()) + { + await Assert.That(results).Count().IsEqualTo(3); + await Assert.That(results[0]).IsEqualTo(1); + await Assert.That(results[1]).IsEqualTo(2); + await Assert.That(results[2]).IsEqualTo(3); + } + } + + [Test] + public async Task CreateFromObservable_WithoutParam_EmitsObservableResults() + { + var command = ReactiveCommand.CreateFromObservable( + () => Observable.Return(42), + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(); + + await Assert.That(results[0]).IsEqualTo(42); + } + + [Test] + public async Task CreateFromObservable_WithoutParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromObservable((Func>)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromObservable_WithParam_PassesParameterToObservable() + { + var command = ReactiveCommand.CreateFromObservable( + param => Observable.Return(param.ToString()), + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(42); + await Assert.That(results[0]).IsEqualTo("42"); + } + + [Test] + public async Task CreateFromObservable_WithParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromObservable((Func>)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromTask_Cancellable_ProperlyCancelsExecution() + { + var tcsStarted = new TaskCompletionSource(); + var tcsCaught = new TaskCompletionSource(); + var tcsFinish = new TaskCompletionSource(); + + var fixture = ReactiveCommand.CreateFromTask( + async token => + { + tcsStarted.TrySetResult(Unit.Default); + try + { + await Task.Delay(10000, token); + } + catch (OperationCanceledException) + { + tcsCaught.TrySetResult(Unit.Default); + await tcsFinish.Task; + throw; + } + }, + outputScheduler: ImmediateScheduler.Instance); + + fixture.ThrownExceptions.Subscribe(_ => { }); + + var disposable = fixture.Execute().Subscribe(); + + await tcsStarted.Task.WaitAsync(TimeSpan.FromSeconds(2)); + disposable.Dispose(); + + await tcsCaught.Task.WaitAsync(TimeSpan.FromSeconds(2)); + tcsFinish.TrySetResult(Unit.Default); + + // Wait for cancellation to complete + await Task.Delay(100); + } + + [Test] + public async Task CreateFromTask_Cancellable_Unit_WithoutParam_ReceivesCancellationToken() + { + CancellationToken? receivedToken = null; + var command = ReactiveCommand.CreateFromTask( + async token => + { + receivedToken = token; + await Task.Delay(10, token); + }, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(); + await Assert.That(receivedToken).IsNotNull(); + } + + [Test] + public async Task CreateFromTask_Cancellable_Unit_WithoutParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromTask((Func)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromTask_Cancellable_Unit_WithParam_ReceivesParameterAndToken() + { + var receivedParam = 0; + CancellationToken? receivedToken = null; + var command = ReactiveCommand.CreateFromTask( + async (param, token) => + { + receivedParam = param; + receivedToken = token; + await Task.Delay(10, token); + }, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(42); + + using (Assert.Multiple()) + { + await Assert.That(receivedParam).IsEqualTo(42); + await Assert.That(receivedToken).IsNotNull(); + } + } + + [Test] + public async Task CreateFromTask_Cancellable_Unit_WithParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromTask((Func)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromTask_Cancellable_WithoutParam_ReceivesCancellationToken() + { + CancellationToken? receivedToken = null; + var command = ReactiveCommand.CreateFromTask( + async token => + { + receivedToken = token; + await Task.Delay(10, token); + return 42; + }, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(); + await Assert.That(receivedToken).IsNotNull(); + } + + [Test] + public async Task CreateFromTask_Cancellable_WithoutParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromTask((Func>)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromTask_Cancellable_WithParam_ReceivesParameterAndToken() + { + var receivedParam = 0; + CancellationToken? receivedToken = null; + var command = ReactiveCommand.CreateFromTask( + async (param, token) => + { + receivedParam = param; + receivedToken = token; + await Task.Delay(10, token); + return param.ToString(); + }, + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(42); + + using (Assert.Multiple()) + { + await Assert.That(receivedParam).IsEqualTo(42); + await Assert.That(receivedToken).IsNotNull(); + await Assert.That(results[0]).IsEqualTo("42"); + } + } + + [Test] + public async Task CreateFromTask_Cancellable_WithParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromTask((Func>)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromTask_Unit_WithoutParam_CompletesSuccessfully() + { + var executed = false; + var command = ReactiveCommand.CreateFromTask( + async () => + { + await Task.CompletedTask; + executed = true; + }, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task CreateFromTask_Unit_WithoutParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromTask((Func)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromTask_Unit_WithParam_PassesParameter() + { + var receivedParam = 0; + var command = ReactiveCommand.CreateFromTask( + async param => + { + await Task.CompletedTask; + receivedParam = param; + }, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(42); + await Assert.That(receivedParam).IsEqualTo(42); + } + + [Test] + public async Task CreateFromTask_Unit_WithParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromTask((Func)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromTask_WithoutParam_ReturnsTaskResult() + { + var command = ReactiveCommand.CreateFromTask( + () => Task.FromResult(42), + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(); + await Assert.That(results[0]).IsEqualTo(42); + } + + [Test] + public async Task CreateFromTask_WithoutParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromTask((Func>)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateFromTask_WithParam_PassesParameterToTask() + { + var command = ReactiveCommand.CreateFromTask( + param => Task.FromResult(param.ToString()), + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(42); + await Assert.That(results[0]).IsEqualTo("42"); + } + + [Test] + public async Task CreateFromTask_WithParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateFromTask((Func>)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateRunInBackground_Action_ExecutesOnBackgroundScheduler() + { + var executed = false; + var command = ReactiveCommand.CreateRunInBackground( + () => executed = true, + backgroundScheduler: ImmediateScheduler.Instance, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task CreateRunInBackground_Action_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateRunInBackground(null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateRunInBackground_ActionWithParam_PassesParameter() + { + var receivedParam = 0; + var command = ReactiveCommand.CreateRunInBackground( + param => receivedParam = param, + backgroundScheduler: ImmediateScheduler.Instance, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(42); + await Assert.That(receivedParam).IsEqualTo(42); + } + + [Test] + public async Task CreateRunInBackground_ActionWithParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateRunInBackground((Action)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateRunInBackground_Func_ReturnsResult() + { + var command = ReactiveCommand.CreateRunInBackground( + () => 42, + backgroundScheduler: ImmediateScheduler.Instance, + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(); + + await Assert.That(results[0]).IsEqualTo(42); + } + + [Test] + public async Task CreateRunInBackground_Func_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateRunInBackground((Func)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateRunInBackground_FuncWithParam_ThrowsOnNullExecute() => + await Assert.ThrowsExactlyAsync(async () => + { + ReactiveCommand.CreateRunInBackground((Func)null!); + await Task.CompletedTask; + }); + + [Test] + public async Task CreateRunInBackground_FuncWithParam_TransformsParameter() + { + var command = ReactiveCommand.CreateRunInBackground( + param => param.ToString(), + backgroundScheduler: ImmediateScheduler.Instance, + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(42); + await Assert.That(results[0]).IsEqualTo("42"); + } + + [Test] + + [TestExecutor] + public async Task Execute_CanBeCancelled() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + var execute = Observables.Unit.Delay(TimeSpan.FromSeconds(1), scheduler); + var command = ReactiveCommand.CreateFromObservable( + () => execute, + outputScheduler: scheduler); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var executed).Subscribe(); + + var sub1 = command.Execute().Subscribe(); + var sub2 = command.Execute().Subscribe(); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + await Assert.That(command.IsExecuting.FirstAsync().Wait()).IsTrue(); + await Assert.That(executed).IsEmpty(); + + sub1.Dispose(); + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + + await Assert.That(executed).Count().IsEqualTo(1); + await Assert.That(command.IsExecuting.FirstAsync().Wait()).IsFalse(); + } + + [Test] + public async Task Execute_LazyEvaluation() + { + var executionCount = 0; + var command = ReactiveCommand.Create( + () => ++executionCount, + outputScheduler: ImmediateScheduler.Instance); + + var execution = command.Execute(); + await Assert.That(executionCount).IsEqualTo(0); + + execution.Subscribe(); + await Assert.That(executionCount).IsEqualTo(1); + } + + [Test] + public async Task Execute_PassesParameters() + { + var parameters = new List(); + var command = ReactiveCommand.Create( + param => parameters.Add(param), + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(1); + await command.Execute(42); + await command.Execute(348); + + using (Assert.Multiple()) + { + await Assert.That(parameters).Count().IsEqualTo(3); + await Assert.That(parameters[0]).IsEqualTo(1); + await Assert.That(parameters[1]).IsEqualTo(42); + await Assert.That(parameters[2]).IsEqualTo(348); + } + } + + [Test] + public async Task Execute_ReenablesAfterCompletion() + { + var command = ReactiveCommand.Create( + () => { }, + outputScheduler: ImmediateScheduler.Instance); + command.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); + + await command.Execute(); + + using (Assert.Multiple()) + { + await Assert.That(canExecute).Count().IsEqualTo(3); + await Assert.That(canExecute[0]).IsTrue(); + await Assert.That(canExecute[1]).IsFalse(); + await Assert.That(canExecute[2]).IsTrue(); + } + } + + [Test] + public async Task Execute_ReenablesAfterFailure() + { + var command = ReactiveCommand.CreateFromObservable( + () => Observable.Throw(new InvalidOperationException("Test error")), + outputScheduler: ImmediateScheduler.Instance); + command.CanExecute.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var canExecute).Subscribe(); + command.ThrownExceptions.Subscribe(); + + command.Execute().Subscribe(_ => { }, _ => { }); + + using (Assert.Multiple()) + { + await Assert.That(canExecute).Count().IsEqualTo(3); + await Assert.That(canExecute[0]).IsTrue(); + await Assert.That(canExecute[1]).IsFalse(); + await Assert.That(canExecute[2]).IsTrue(); + } + } + + [Test] + public async Task Execute_TicksMultipleResults() + { + var command = ReactiveCommand.CreateFromObservable( + () => new[] { 1, 2, 3 }.ToObservable(), + outputScheduler: ImmediateScheduler.Instance); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe(); + + await command.Execute(); + + using (Assert.Multiple()) + { + await Assert.That(results).Count().IsEqualTo(3); + await Assert.That(results[0]).IsEqualTo(1); + await Assert.That(results[1]).IsEqualTo(2); + await Assert.That(results[2]).IsEqualTo(3); + } + } + + [Test] + + [TestExecutor] + public async Task ICommand_CanExecute_IsFalseWhileExecuting() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + var execute = Observables.Unit.Delay(TimeSpan.FromSeconds(1), scheduler); + ICommand command = ReactiveCommand.CreateFromObservable( + () => execute, + outputScheduler: scheduler); + + await Assert.That(command.CanExecute(null)).IsTrue(); + + command.Execute(null); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + await Assert.That(command.CanExecute(null)).IsFalse(); + } + + [Test] + public async Task ICommand_CanExecute_ReturnsCorrectValue() + { + var canExecuteSubject = new BehaviorSubject(false); + ICommand command = ReactiveCommand.Create( + () => { }, + canExecuteSubject, + ImmediateScheduler.Instance); + + await Assert.That(command.CanExecute(null)).IsFalse(); + + canExecuteSubject.OnNext(true); + await Assert.That(command.CanExecute(null)).IsTrue(); + } + + [Test] + public async Task ICommand_CanExecuteChanged_RaisesEvents() + { + var canExecuteSubject = new BehaviorSubject(false); + ICommand command = ReactiveCommand.Create( + () => { }, + canExecuteSubject, + ImmediateScheduler.Instance); + var canExecuteChanged = new List(); + command.CanExecuteChanged += (_, __) => canExecuteChanged.Add(command.CanExecute(null)); + + canExecuteSubject.OnNext(true); + canExecuteSubject.OnNext(false); + + using (Assert.Multiple()) + { + await Assert.That(canExecuteChanged).Count().IsEqualTo(2); + await Assert.That(canExecuteChanged[0]).IsTrue(); + await Assert.That(canExecuteChanged[1]).IsFalse(); + } + } + + [Test] + public async Task ICommand_Execute_InvokesCommand() + { + var executed = false; + ICommand command = ReactiveCommand.Create( + () => executed = true, + outputScheduler: ImmediateScheduler.Instance); + + command.Execute(null); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task ICommand_Execute_PassesParameter() + { + var receivedParam = 0; + ICommand command = ReactiveCommand.Create( + param => receivedParam = param, + outputScheduler: ImmediateScheduler.Instance); + + command.Execute(42); + await Assert.That(receivedParam).IsEqualTo(42); + } + + [Test] + public async Task ICommand_Execute_ThrowsOnIncorrectParameterType() + { + ICommand command = ReactiveCommand.Create( + _ => { }, + outputScheduler: ImmediateScheduler.Instance); + + var ex = Assert.Throws(() => command.Execute("wrong type")); + await Assert.That(ex!.Message).Contains("System.Int32"); + await Assert.That(ex.Message).Contains("System.String"); + } + + [Test] + public async Task ICommand_Execute_WorksWithNullableParameters() + { + int? receivedValue = null; + ICommand command = ReactiveCommand.Create( + param => receivedValue = param, + outputScheduler: ImmediateScheduler.Instance); + + command.Execute(42); + await Assert.That(receivedValue).IsEqualTo(42); + + command.Execute(null); + await Assert.That(receivedValue).IsNull(); + } + + [Test] + public async Task InvokeCommand_ICommand_InvokesCommand() + { + var executionCount = 0; + ICommand command = ReactiveCommand.Create( + () => ++executionCount, + outputScheduler: ImmediateScheduler.Instance); + var source = new Subject(); + source.InvokeCommand(command); + + source.OnNext(Unit.Default); + await Assert.That(executionCount).IsEqualTo(1); + + source.OnNext(Unit.Default); + await Assert.That(executionCount).IsEqualTo(2); + } + + [Test] + public async Task InvokeCommand_ICommand_PassesParameter() + { + var receivedParams = new List(); + ICommand command = ReactiveCommand.Create( + param => receivedParams.Add(param), + outputScheduler: ImmediateScheduler.Instance); + var source = new Subject(); + source.InvokeCommand(command); + + source.OnNext(42); + source.OnNext(100); + + using (Assert.Multiple()) + { + await Assert.That(receivedParams).Count().IsEqualTo(2); + await Assert.That(receivedParams[0]).IsEqualTo(42); + await Assert.That(receivedParams[1]).IsEqualTo(100); + } + } + + [Test] + public async Task InvokeCommand_ICommand_RespectsCanExecute() + { + var executed = false; + var canExecute = new BehaviorSubject(false); + ICommand command = ReactiveCommand.Create( + () => executed = true, + canExecute, + ImmediateScheduler.Instance); + var source = new Subject(); + source.InvokeCommand(command); + + source.OnNext(Unit.Default); + await Assert.That(executed).IsFalse(); + + canExecute.OnNext(true); + source.OnNext(Unit.Default); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task InvokeCommand_ICommand_WorksWithColdObservable() + { + var executionCount = 0; + ICommand command = ReactiveCommand.Create( + () => ++executionCount, + outputScheduler: ImmediateScheduler.Instance); + var source = Observable.Return(Unit.Default); + source.InvokeCommand(command); + + await Assert.That(executionCount).IsEqualTo(1); + } + + [Test] + public async Task InvokeCommand_ICommandInTarget_InvokesCommand() + { + var executionCount = 0; + var target = new ICommandHolder(); + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + target.TheCommand = ReactiveCommand.Create( + () => ++executionCount, + outputScheduler: ImmediateScheduler.Instance); + + source.OnNext(Unit.Default); + await Assert.That(executionCount).IsEqualTo(1); + + source.OnNext(Unit.Default); + await Assert.That(executionCount).IsEqualTo(2); + } + + [Test] + public async Task InvokeCommand_ICommandInTarget_PassesParameter() + { + var target = new ICommandHolder(); + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + var command = new FakeCommand(); + target.TheCommand = command; + + source.OnNext(42); + + using (Assert.Multiple()) + { + await Assert.That(command.CanExecuteParameter).IsEqualTo(42); + await Assert.That(command.ExecuteParameter).IsEqualTo(42); + } + } + + [Test] + public async Task InvokeCommand_ICommandInTarget_RespectsCanExecute() + { + var executed = false; + var canExecute = new BehaviorSubject(false); + var target = new ICommandHolder(); + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + target.TheCommand = ReactiveCommand.Create( + () => executed = true, + canExecute, + ImmediateScheduler.Instance); + + source.OnNext(Unit.Default); + await Assert.That(executed).IsFalse(); + + canExecute.OnNext(true); + source.OnNext(Unit.Default); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task InvokeCommand_ICommandInTarget_RespectsCanExecuteWindow() + { + var executed = false; + var canExecute = new BehaviorSubject(false); + var target = new ICommandHolder(); + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + target.TheCommand = ReactiveCommand.Create( + () => executed = true, + canExecute, + ImmediateScheduler.Instance); + + source.OnNext(Unit.Default); + await Assert.That(executed).IsFalse(); + + // When window reopens, previous requests should NOT execute + canExecute.OnNext(true); + await Assert.That(executed).IsFalse(); + } + + [Test] + public async Task InvokeCommand_ICommandInTarget_SwallowsExceptions() + { + var count = 0; + var target = new ICommandHolder(); + var command = ReactiveCommand.Create( + () => + { + ++count; + throw new InvalidOperationException(); + }, + outputScheduler: ImmediateScheduler.Instance); + command.ThrownExceptions.Subscribe(); + target.TheCommand = command; + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + + source.OnNext(Unit.Default); + source.OnNext(Unit.Default); + + await Assert.That(count).IsEqualTo(2); + } + + [Test] + public async Task InvokeCommand_ReactiveCommand_InvokesCommand() + { + var executionCount = 0; + var command = ReactiveCommand.Create( + () => ++executionCount, + outputScheduler: ImmediateScheduler.Instance); + var source = new Subject(); + source.InvokeCommand(command); + + source.OnNext(Unit.Default); + await Assert.That(executionCount).IsEqualTo(1); + + source.OnNext(Unit.Default); + await Assert.That(executionCount).IsEqualTo(2); + } + + [Test] + public async Task InvokeCommand_ReactiveCommand_PassesParameter() + { + var receivedParams = new List(); + var command = ReactiveCommand.Create( + param => receivedParams.Add(param), + outputScheduler: ImmediateScheduler.Instance); + var source = new Subject(); + source.InvokeCommand(command); + + source.OnNext(42); + source.OnNext(100); + + using (Assert.Multiple()) + { + await Assert.That(receivedParams).Count().IsEqualTo(2); + await Assert.That(receivedParams[0]).IsEqualTo(42); + await Assert.That(receivedParams[1]).IsEqualTo(100); + } + } + + [Test] + public async Task InvokeCommand_ReactiveCommand_RespectsCanExecute() + { + var executed = false; + var canExecute = new BehaviorSubject(false); + var command = ReactiveCommand.Create( + () => executed = true, + canExecute, + ImmediateScheduler.Instance); + var source = new Subject(); + source.InvokeCommand(command); + + source.OnNext(Unit.Default); + await Assert.That(executed).IsFalse(); + + canExecute.OnNext(true); + source.OnNext(Unit.Default); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task InvokeCommand_ReactiveCommand_RespectsCanExecuteWindow() + { + var executed = false; + var canExecute = new BehaviorSubject(false); + var command = ReactiveCommand.Create( + () => executed = true, + canExecute, + ImmediateScheduler.Instance); + var source = new Subject(); + source.InvokeCommand(command); + + source.OnNext(Unit.Default); + await Assert.That(executed).IsFalse(); + + // When window reopens, previous requests should NOT execute + canExecute.OnNext(true); + await Assert.That(executed).IsFalse(); + } + + [Test] + public async Task InvokeCommand_ReactiveCommand_SwallowsExceptions() + { + var count = 0; + var command = ReactiveCommand.Create( + () => + { + ++count; + throw new InvalidOperationException(); + }, + outputScheduler: ImmediateScheduler.Instance); + command.ThrownExceptions.Subscribe(); + var source = new Subject(); + source.InvokeCommand(command); + + source.OnNext(Unit.Default); + source.OnNext(Unit.Default); + + await Assert.That(count).IsEqualTo(2); + } + + [Test] + public async Task InvokeCommand_ReactiveCommandInTarget_InvokesCommand() + { + var executionCount = 0; + var target = new ReactiveCommandHolder(); + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + target.TheCommand = ReactiveCommand.Create( + _ => ++executionCount, + outputScheduler: ImmediateScheduler.Instance); + + source.OnNext(0); + await Assert.That(executionCount).IsEqualTo(1); + + source.OnNext(0); + await Assert.That(executionCount).IsEqualTo(2); + } + + [Test] + public async Task InvokeCommand_ReactiveCommandInTarget_PassesParameter() + { + var receivedParam = 0; + var target = new ReactiveCommandHolder(); + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + target.TheCommand = ReactiveCommand.Create( + param => receivedParam = param, + outputScheduler: ImmediateScheduler.Instance); + + source.OnNext(42); + await Assert.That(receivedParam).IsEqualTo(42); + } + + [Test] + public async Task InvokeCommand_ReactiveCommandInTarget_RespectsCanExecute() + { + var executed = false; + var canExecute = new BehaviorSubject(false); + var target = new ReactiveCommandHolder(); + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + target.TheCommand = ReactiveCommand.Create( + _ => executed = true, + canExecute, + ImmediateScheduler.Instance); + + source.OnNext(0); + await Assert.That(executed).IsFalse(); + + canExecute.OnNext(true); + source.OnNext(0); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task InvokeCommand_ReactiveCommandInTarget_RespectsCanExecuteWindow() + { + var executed = false; + var canExecute = new BehaviorSubject(false); + var target = new ReactiveCommandHolder(); + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + target.TheCommand = ReactiveCommand.Create( + _ => executed = true, + canExecute, + ImmediateScheduler.Instance); + + source.OnNext(0); + await Assert.That(executed).IsFalse(); + + // When window reopens, previous requests should NOT execute + canExecute.OnNext(true); + await Assert.That(executed).IsFalse(); + } + + [Test] + public async Task InvokeCommand_ReactiveCommandInTarget_SwallowsExceptions() + { + var count = 0; + var target = new ReactiveCommandHolder + { + TheCommand = ReactiveCommand.Create( + _ => + { + ++count; + throw new InvalidOperationException(); + }, + outputScheduler: ImmediateScheduler.Instance) + }; + target.TheCommand.ThrownExceptions.Subscribe(); + var source = new Subject(); + source.InvokeCommand(target, x => x.TheCommand!); + + source.OnNext(0); + source.OnNext(0); + + await Assert.That(count).IsEqualTo(2); + } + + [Test] + + [TestExecutor] + public async Task IsExecuting_HandlesMultipleInFlightExecutions() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + var execute = Observables.Unit.Delay(TimeSpan.FromMilliseconds(500), scheduler); + var command = ReactiveCommand.CreateFromObservable( + () => execute, + outputScheduler: scheduler); + command.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var executed).Subscribe(); + + var sub1 = command.Execute().Subscribe(); + var sub2 = command.Execute().Subscribe(); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + await Assert.That(command.IsExecuting.FirstAsync().Wait()).IsTrue(); + await Assert.That(executed).IsEmpty(); + } + + [Test] + public async Task IsExecuting_IsBehavioral() + { + var command = ReactiveCommand.Create( + () => { }, + outputScheduler: ImmediateScheduler.Instance); + command.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var isExecuting).Subscribe(); + + using (Assert.Multiple()) + { + await Assert.That(isExecuting).Count().IsEqualTo(1); + await Assert.That(isExecuting[0]).IsFalse(); + } + } + + [Test] + public async Task IsExecuting_RemainsTrue_UntilExecutionCompletes() + { + var executeSubject = new Subject(); + var command = ReactiveCommand.CreateFromObservable( + () => executeSubject, + outputScheduler: ImmediateScheduler.Instance); + + command.Execute().Subscribe(); + + await Assert.That(command.IsExecuting.FirstAsync().Wait()).IsTrue(); + + executeSubject.OnNext(Unit.Default); + await Assert.That(command.IsExecuting.FirstAsync().Wait()).IsTrue(); + + executeSubject.OnCompleted(); + await Assert.That(command.IsExecuting.FirstAsync().Wait()).IsFalse(); + } + + [Test] + + [TestExecutor] + public async Task IsExecuting_TicksWhileExecuting() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + var execute = Observables.Unit.Delay(TimeSpan.FromSeconds(1), scheduler); + var command = ReactiveCommand.CreateFromObservable( + () => execute, + outputScheduler: scheduler); + command.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var isExecuting).Subscribe(); + + command.Execute().Subscribe(); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + using (Assert.Multiple()) + { + await Assert.That(isExecuting).Count().IsEqualTo(2); + await Assert.That(isExecuting[0]).IsFalse(); + await Assert.That(isExecuting[1]).IsTrue(); + } + } + + [Test] + public async Task Observable_Subscription_ProperLifecycle() + { + var executed = 0; + var command = ReactiveCommand.Create( + () => ++executed, + outputScheduler: ImmediateScheduler.Instance); + + var subscription = command.Subscribe(_ => { }); + await command.Execute(); + + await Assert.That(executed).IsEqualTo(1); + + subscription.Dispose(); + await command.Execute(); + + // Should still execute even after subscription disposal + await Assert.That(executed).IsEqualTo(2); + } + + [Test] + + [TestExecutor] + public async Task ReactiveSetpoint_AsyncMethodExecution() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + RxSchedulers.TaskpoolScheduler = scheduler; + + var fooVm = new FooViewModel(new Foo()); + + await Assert.That(fooVm.Foo.Value).IsEqualTo(42); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(11)); + await Assert.That(fooVm.Foo.Value).IsEqualTo(0); + + fooVm.Setpoint = 123; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(5)); + await Assert.That(fooVm.Foo.Value).IsEqualTo(0); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(6)); + await Assert.That(fooVm.Foo.Value).IsEqualTo(123); + } + + [Test] + public async Task Scheduler_BackgroundCommandUsesBackgroundScheduler() + { + var backgroundScheduler = ImmediateScheduler.Instance; + var executed = false; + var command = ReactiveCommand.CreateRunInBackground( + () => executed = true, + backgroundScheduler: backgroundScheduler, + outputScheduler: ImmediateScheduler.Instance); + + await command.Execute(); + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task Scheduler_ResultsDeliveredOnOutputScheduler() + { + var scheduler = TestContext.Current!.GetScheduler(); + var command = ReactiveCommand.CreateFromObservable( + () => Observables.Unit, + outputScheduler: scheduler); + var executed = false; + + command.Execute().ObserveOn(scheduler).Subscribe(_ => executed = true); + + await Assert.That(executed).IsTrue(); + } + + [Test] + public async Task Task_Cancellation_HandlesProperCancellationFlow() + { + var tcsStarted = new TaskCompletionSource(); + var tcsCaught = new TaskCompletionSource(); + var tcsFinish = new TaskCompletionSource(); + var statusTrail = new List<(int Position, string Status)>(); + var position = 0; + + var command = ReactiveCommand.CreateFromTask( + async token => + { + statusTrail.Add((Interlocked.Increment(ref position) - 1, "started command")); + tcsStarted.TrySetResult(Unit.Default); + try + { + await Task.Delay(10000, token); + } + catch (OperationCanceledException) + { + statusTrail.Add((Interlocked.Increment(ref position) - 1, "cancelling command")); + tcsCaught.TrySetResult(Unit.Default); + await tcsFinish.Task; + statusTrail.Add((Interlocked.Increment(ref position) - 1, "finished cancelling")); + throw; + } + + return Unit.Default; + }, + outputScheduler: ImmediateScheduler.Instance); + + Exception? exception = null; + command.ThrownExceptions.Subscribe(ex => exception = ex); + var latestIsExecutingValue = false; + command.IsExecuting.Subscribe(isExec => + { + statusTrail.Add((Interlocked.Increment(ref position) - 1, $"executing = {isExec}")); + Volatile.Write(ref latestIsExecutingValue, isExec); + }); + + var disposable = command.Execute().Subscribe(); + + await tcsStarted.Task.WaitAsync(TimeSpan.FromSeconds(2)); + await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); + + disposable.Dispose(); + + await tcsCaught.Task.WaitAsync(TimeSpan.FromSeconds(2)); + await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); + + tcsFinish.TrySetResult(Unit.Default); + await Task.Delay(100); + + using (Assert.Multiple()) + { + await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsFalse(); + await Assert.That(exception).IsTypeOf(); + await Assert.That(statusTrail).IsEquivalentTo( + [ + (0, "executing = False"), + (1, "executing = True"), + (2, "started command"), + (3, "cancelling command"), + (4, "finished cancelling"), + (5, "executing = False") + ]); + } + } + + [Test] + public async Task Task_Completion_HandlesProperCompletionFlow() + { + var tcsStarted = new TaskCompletionSource(); + var tcsFinished = new TaskCompletionSource(); + var tcsContinue = new TaskCompletionSource(); + var statusTrail = new List<(int Position, string Status)>(); + var position = 0; + + var command = ReactiveCommand.CreateFromTask( + async cts => + { + statusTrail.Add((Interlocked.Increment(ref position) - 1, "started command")); + tcsStarted.TrySetResult(Unit.Default); + try + { + await Task.Delay(1000, cts); + } + catch (OperationCanceledException) + { + statusTrail.Add((Interlocked.Increment(ref position) - 1, "cancelling command")); + await Task.Delay(5000, CancellationToken.None); + statusTrail.Add((Interlocked.Increment(ref position) - 1, "finished cancelling")); + throw; + } + + statusTrail.Add((Interlocked.Increment(ref position) - 1, "finished command")); + tcsFinished.TrySetResult(Unit.Default); + await tcsContinue.Task; + return Unit.Default; + }, + outputScheduler: ImmediateScheduler.Instance); + + Exception? exception = null; + command.ThrownExceptions.Subscribe(ex => exception = ex); + var latestIsExecutingValue = false; + 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); + + await tcsStarted.Task.WaitAsync(TimeSpan.FromSeconds(2)); + await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); + + await tcsFinished.Task.WaitAsync(TimeSpan.FromSeconds(2)); + await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); + + tcsContinue.TrySetResult(Unit.Default); + await Task.Delay(100); + + using (Assert.Multiple()) + { + await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsFalse(); + await Assert.That(result).IsTrue(); + await Assert.That(exception).IsNull(); + await Assert.That(statusTrail).IsEquivalentTo( + [ + (0, "executing = False"), + (1, "executing = True"), + (2, "started command"), + (3, "finished command"), + (4, "executing = False") + ]); + } + } + + [Test] + public async Task Task_Exception_HandlesExceptionFlow() + { + var tcsStart = new TaskCompletionSource(); + var command = ReactiveCommand.CreateFromTask( + async _ => + { + await tcsStart.Task; + throw new Exception("Task exception"); + }, + outputScheduler: ImmediateScheduler.Instance); + command.IsExecuting.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var isExecuting).Subscribe(); + command.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var exceptions) + .Subscribe(); + + command.Execute().Subscribe(); + + await Task.Delay(100); + tcsStart.SetResult(Unit.Default); + await Task.Delay(100); + + using (Assert.Multiple()) + { + await Assert.That(isExecuting[0]).IsFalse(); + await Assert.That(isExecuting[1]).IsTrue(); + await Assert.That(exceptions).Count().IsEqualTo(1); + await Assert.That(exceptions[0].Message).IsEqualTo("Task exception"); + } + } + + [Test] + public async Task ThrownExceptions_CapturesLambdaExceptions() + { + var command = ReactiveCommand.CreateFromObservable( + () => throw new InvalidOperationException("Lambda error"), + outputScheduler: ImmediateScheduler.Instance); + command.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var exceptions) + .Subscribe(); + + command.Execute().Subscribe(_ => { }, _ => { }); + + await Assert.That(exceptions).Count().IsEqualTo(1); + await Assert.That(exceptions[0]).IsTypeOf(); + await Assert.That(exceptions[0].Message).IsEqualTo("Lambda error"); + } + + [Test] + public async Task ThrownExceptions_CapturesObservableExceptions() + { + var command = ReactiveCommand.CreateFromObservable( + () => Observable.Throw(new InvalidOperationException("Test error")), + outputScheduler: ImmediateScheduler.Instance); + command.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var exceptions) + .Subscribe(); + + command.Execute().Subscribe(_ => { }, _ => { }); + + await Assert.That(exceptions).Count().IsEqualTo(1); + await Assert.That(exceptions[0]).IsTypeOf(); + await Assert.That(exceptions[0].Message).IsEqualTo("Test error"); + } + + [Test] + public async Task ThrownExceptions_DeliveredOnOutputScheduler() + { + var scheduler = TestContext.Current!.GetScheduler(); + var command = ReactiveCommand.CreateFromObservable( + () => Observable.Throw(new InvalidOperationException()), + outputScheduler: scheduler); + Exception? exception = null; + command.ThrownExceptions.Subscribe(ex => exception = ex); + + command.Execute().Subscribe(_ => { }, _ => { }); + + await Assert.That(exception).IsTypeOf(); + } + + [Test] + public async Task ThrownExceptions_PropagatesTaskExceptions() + { + var tcsStart = new TaskCompletionSource(); + var command = ReactiveCommand.CreateFromTask( + async _ => + { + await tcsStart.Task; + throw new Exception("Task exception"); + }, + outputScheduler: ImmediateScheduler.Instance); + command.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var exceptions) + .Subscribe(); + + command.Execute().Subscribe(); + + await Task.Delay(100); + tcsStart.SetResult(Unit.Default); + await Task.Delay(100); + + await Assert.That(exceptions).Count().IsEqualTo(1); + await Assert.That(exceptions[0].Message).IsEqualTo("Task exception"); + } +} diff --git a/src/tests/ReactiveUI.Tests/Comparers/OrderedComparerTests.cs b/src/tests/ReactiveUI.Tests/Comparers/OrderedComparerTests.cs index eebadfc7c0..0710e11b8d 100644 --- a/src/tests/ReactiveUI.Tests/Comparers/OrderedComparerTests.cs +++ b/src/tests/ReactiveUI.Tests/Comparers/OrderedComparerTests.cs @@ -5,121 +5,124 @@ using System.Diagnostics; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Comparers; public class OrderedComparerTests { /// - /// A general smoke test. + /// Test for checking that chaining the onto regular IComparable works. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task SmokeTest() + public async Task ChainOntoRegularIComparables() { - var adam = new Employee { Name = "Adam", Age = 50, Salary = 125 }; - var alice = new Employee { Name = "Alice", Age = 25, Salary = 100 }; - var bob = new Employee { Name = "Bob", Age = 30, Salary = 75 }; - var carol = new Employee { Name = "Carol", Age = 35, Salary = 100 }; - var xavier = new Employee { Name = "Xavier", Age = 35, Salary = 100 }; - - var employees = new List - { - adam, - alice, - bob, - carol, - xavier - }; - - employees.Sort(OrderedComparer.OrderBy(static x => x.Name)); - await Assert.That(employees.SequenceEqual([adam, alice, bob, carol, xavier])).IsTrue(); - - employees.Sort( - OrderedComparer - .OrderByDescending(static x => x.Age) - .ThenBy(static x => x.Name)); - await Assert.That(employees.SequenceEqual([adam, carol, xavier, bob, alice])).IsTrue(); + var items = new List { "aaa", "AAA", "abb", "aaaa" }; + var comparer = StringComparer.OrdinalIgnoreCase; - employees.Sort( - OrderedComparer - .OrderByDescending(static x => x.Salary) - .ThenBy( - static x => x.Name, - StringComparer.OrdinalIgnoreCase)); - await Assert.That(employees.SequenceEqual([adam, alice, carol, xavier, bob])).IsTrue(); + items.Sort(comparer); + await Assert.That( + items.SequenceEqual( + ["AAA", "aaa", "aaaa", "abb"], + StringComparer.OrdinalIgnoreCase)).IsTrue(); - employees.Sort( - OrderedComparer - .OrderByDescending(static x => x.Age) - .ThenByDescending(static x => x.Salary) - .ThenBy(static x => x.Name)); - await Assert.That(employees.SequenceEqual([adam, carol, xavier, bob, alice])).IsTrue(); + items.Sort( + comparer.ThenByDescending( + static x => x, + StringComparer.Ordinal)); + await Assert.That( + items.SequenceEqual( + ["aaa", "AAA", "aaaa", "abb"], + StringComparer.Ordinal)).IsTrue(); } /// - /// A test which determines if customer comparers work. + /// A test which determines if customer comparers work. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task CustomComparerTest() { List items = ["aaa", "AAA", "abb", "aaaa"]; items.Sort( - OrderedComparer.OrderBy( - static x => x, - StringComparer.Ordinal)); + OrderedComparer.OrderBy( + static x => x, + StringComparer.Ordinal)); await Assert.That(items.SequenceEqual(["AAA", "aaa", "aaaa", "abb"])).IsTrue(); items.Sort( - OrderedComparer.OrderByDescending(static x => x.Length).ThenBy( - static x => x, - StringComparer.Ordinal)); + OrderedComparer.OrderByDescending(static x => x.Length).ThenBy( + static x => x, + StringComparer.Ordinal)); await Assert.That(items.SequenceEqual(["aaaa", "AAA", "aaa", "abb"])).IsTrue(); items.Sort( - OrderedComparer.OrderBy(static x => x.Length).ThenBy( - static x => x, - StringComparer.Ordinal)); + OrderedComparer.OrderBy(static x => x.Length).ThenBy( + static x => x, + StringComparer.Ordinal)); await Assert.That(items.SequenceEqual(["AAA", "aaa", "abb", "aaaa"])).IsTrue(); items.Sort( - OrderedComparer.OrderBy(static x => x.Length).ThenBy( - static x => x, - StringComparer.OrdinalIgnoreCase)); - await Assert.That(items.SequenceEqual( - ["AAA", "AAA", "abb", "aaaa"], - StringComparer.OrdinalIgnoreCase)).IsTrue(); + OrderedComparer.OrderBy(static x => x.Length).ThenBy( + static x => x, + StringComparer.OrdinalIgnoreCase)); + await Assert.That( + items.SequenceEqual( + ["AAA", "AAA", "abb", "aaaa"], + StringComparer.OrdinalIgnoreCase)).IsTrue(); } /// - /// Test for checking that chaining the onto regular IComparable works. + /// A general smoke test. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ChainOntoRegularIComparables() + public async Task SmokeTest() { - var items = new List { "aaa", "AAA", "abb", "aaaa" }; - var comparer = StringComparer.OrdinalIgnoreCase; + var adam = new Employee { Name = "Adam", Age = 50, Salary = 125 }; + var alice = new Employee { Name = "Alice", Age = 25, Salary = 100 }; + var bob = new Employee { Name = "Bob", Age = 30, Salary = 75 }; + var carol = new Employee { Name = "Carol", Age = 35, Salary = 100 }; + var xavier = new Employee { Name = "Xavier", Age = 35, Salary = 100 }; - items.Sort(comparer); - await Assert.That(items.SequenceEqual( - ["AAA", "aaa", "aaaa", "abb"], - StringComparer.OrdinalIgnoreCase)).IsTrue(); + var employees = new List + { + adam, + alice, + bob, + carol, + xavier + }; - items.Sort( - comparer.ThenByDescending( - static x => x, - StringComparer.Ordinal)); - await Assert.That(items.SequenceEqual( - ["aaa", "AAA", "aaaa", "abb"], - StringComparer.Ordinal)).IsTrue(); + employees.Sort(OrderedComparer.OrderBy(static x => x.Name)); + await Assert.That(employees.SequenceEqual([adam, alice, bob, carol, xavier])).IsTrue(); + + employees.Sort( + OrderedComparer + .OrderByDescending(static x => x.Age) + .ThenBy(static x => x.Name)); + await Assert.That(employees.SequenceEqual([adam, carol, xavier, bob, alice])).IsTrue(); + + employees.Sort( + OrderedComparer + .OrderByDescending(static x => x.Salary) + .ThenBy( + static x => x.Name, + StringComparer.OrdinalIgnoreCase)); + await Assert.That(employees.SequenceEqual([adam, alice, carol, xavier, bob])).IsTrue(); + + employees.Sort( + OrderedComparer + .OrderByDescending(static x => x.Age) + .ThenByDescending(static x => x.Salary) + .ThenBy(static x => x.Name)); + await Assert.That(employees.SequenceEqual([adam, carol, xavier, bob, alice])).IsTrue(); } /// - /// Test that checks it works with anonymous types. + /// Test that checks it works with anonymous types. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WorksWithAnonymousTypes() { @@ -133,10 +136,10 @@ public async Task WorksWithAnonymousTypes() [DebuggerDisplay("{Name}")] private class Employee { - public string? Name { get; init; } - public int Age { get; init; } + public string? Name { get; init; } + public int Salary { get; init; } } } diff --git a/src/tests/ReactiveUI.Tests/Core/AttributeTests.cs b/src/tests/ReactiveUI.Tests/Core/AttributeTests.cs new file mode 100644 index 0000000000..ef683666d5 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Core/AttributeTests.cs @@ -0,0 +1,139 @@ +// Copyright (c) 2025 .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.Core; + +/// +/// Tests for ReactiveUI attribute types. +/// +public class AttributeTests +{ + /// + /// Tests that LocalizableAttribute stores false value correctly. + /// + /// A representing the asynchronous operation. + [Test] + public async Task LocalizableAttribute_FalseValue_StoresFalse() + { + // Act + var attribute = new LocalizableAttribute(false); + + // Assert + await Assert.That(attribute.IsLocalizable).IsFalse(); + } + + /// + /// Tests that LocalizableAttribute stores true value correctly. + /// + /// A representing the asynchronous operation. + [Test] + public async Task LocalizableAttribute_TrueValue_StoresTrue() + { + // Act + var attribute = new LocalizableAttribute(true); + + // Assert + await Assert.That(attribute.IsLocalizable).IsTrue(); + } + + /// + /// Tests that PreserveAttribute can be instantiated. + /// + /// A representing the asynchronous operation. + [Test] + public async Task PreserveAttribute_Constructor_CreatesInstance() + { + // Act + var attribute = new PreserveAttribute(); + + // Assert + await Assert.That(attribute).IsNotNull(); + await Assert.That(attribute).IsTypeOf(); + } + + /// + /// Tests that SingleInstanceViewAttribute has correct AttributeUsage. + /// + /// A representing the asynchronous operation. + [Test] + public async Task SingleInstanceViewAttribute_AttributeUsage_IsClass() + { + // Arrange + var attributeType = typeof(SingleInstanceViewAttribute); + + // Act + var attributeUsage = attributeType.GetCustomAttribute(); + + // Assert + await Assert.That(attributeUsage).IsNotNull(); + await Assert.That(attributeUsage!.ValidOn).IsEqualTo(AttributeTargets.Class); + } + + /// + /// Tests that SingleInstanceViewAttribute can be instantiated. + /// + /// A representing the asynchronous operation. + [Test] + public async Task SingleInstanceViewAttribute_Constructor_CreatesInstance() + { + // Act + var attribute = new SingleInstanceViewAttribute(); + + // Assert + await Assert.That(attribute).IsNotNull(); + await Assert.That(attribute).IsTypeOf(); + } + + /// + /// Tests that ViewContractAttribute has correct AttributeUsage. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ViewContractAttribute_AttributeUsage_IsClass() + { + // Arrange + var attributeType = typeof(ViewContractAttribute); + + // Act + var attributeUsage = attributeType.GetCustomAttribute(); + + // Assert + await Assert.That(attributeUsage).IsNotNull(); + await Assert.That(attributeUsage!.ValidOn).IsEqualTo(AttributeTargets.Class); + } + + /// + /// Tests that ViewContractAttribute correctly stores contract value. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ViewContractAttribute_Constructor_StoresContractValue() + { + // Arrange + const string expectedContract = "TestContract"; + + // Act + var attribute = new ViewContractAttribute(expectedContract); + + // Assert + await Assert.That(attribute.Contract).IsEqualTo(expectedContract); + } + + /// + /// Tests that ViewContractAttribute handles null contract. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ViewContractAttribute_NullContract_StoresNull() + { + // Act + var attribute = new ViewContractAttribute(null!); + + // Assert + await Assert.That(attribute.Contract).IsNull(); + } +} diff --git a/src/tests/ReactiveUI.Tests/Core/DataStructureTests.cs b/src/tests/ReactiveUI.Tests/Core/DataStructureTests.cs new file mode 100644 index 0000000000..29820839b9 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Core/DataStructureTests.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2025 .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.Core; + +/// +/// Tests for ReactiveUI data structure types. +/// +public class DataStructureTests +{ + /// + /// Tests ObservedChange constructor and properties. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ObservedChange_Constructor_StoresAllValues() + { + // Arrange + const string sender = "test sender"; + var expression = System.Linq.Expressions.Expression.Constant("test"); + const int value = 42; + + // Act + var observedChange = new ObservedChange( + sender, + expression, + value); + + using (Assert.Multiple()) + { + // Assert + await Assert.That(observedChange.Sender).IsEqualTo(sender); + await Assert.That(observedChange.Expression).IsEqualTo(expression); + await Assert.That(observedChange.Value).IsEqualTo(value); + } + } + + /// + /// Tests ReactivePropertyChangedEventArgs constructor and properties. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ReactivePropertyChangedEventArgs_Constructor_StoresValues() + { + // Arrange + const string sender = "test sender"; + const string propertyName = "TestProperty"; + + // Act + var eventArgs = new ReactivePropertyChangedEventArgs( + sender, + propertyName); + + using (Assert.Multiple()) + { + // Assert + await Assert.That(eventArgs.Sender).IsEqualTo(sender); + await Assert.That(eventArgs.PropertyName).IsEqualTo(propertyName); + } + } +} diff --git a/src/tests/ReactiveUI.Tests/Core/EnumTests.cs b/src/tests/ReactiveUI.Tests/Core/EnumTests.cs new file mode 100644 index 0000000000..dbeb6ec3a0 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Core/EnumTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2025 .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.Core; + +/// +/// Tests for ReactiveUI enum types to ensure backwards compatibility. +/// +public class EnumTests +{ + /// + /// Tests BindingDirection enum values for backwards compatibility. + /// + /// A representing the asynchronous operation. + [Test] + [SuppressMessage( + "Usage", + "TUnitAssertions0005:Assert.That(...) should not be used with a constant value", + Justification = "Verifying enum values remain constant for backwards compatibility")] + public async Task BindingDirection_EnumValues_AreConstant() + { + using (Assert.Multiple()) + { + // Assert + await Assert.That((int)BindingDirection.OneWay).IsEqualTo(0); + await Assert.That((int)BindingDirection.TwoWay).IsEqualTo(1); + await Assert.That((int)BindingDirection.AsyncOneWay).IsEqualTo(2); + } + } + + /// + /// Tests TriggerUpdate enum values for backwards compatibility. + /// + /// A representing the asynchronous operation. + [Test] + [SuppressMessage( + "Usage", + "TUnitAssertions0005:Assert.That(...) should not be used with a constant value", + Justification = "Verifying enum values remain constant for backwards compatibility")] + public async Task TriggerUpdate_EnumValues_AreConstant() + { + using (Assert.Multiple()) + { + // Assert + await Assert.That((int)TriggerUpdate.ViewToViewModel).IsEqualTo(0); + await Assert.That((int)TriggerUpdate.ViewModelToView).IsEqualTo(1); + } + } +} diff --git a/src/tests/ReactiveUI.Tests/Core/ExceptionTests.cs b/src/tests/ReactiveUI.Tests/Core/ExceptionTests.cs new file mode 100644 index 0000000000..c28450d6a4 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Core/ExceptionTests.cs @@ -0,0 +1,124 @@ +// Copyright (c) 2025 .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.Core; + +/// +/// Tests for ReactiveUI exception types. +/// +public class ExceptionTests +{ + /// + /// Tests that UnhandledErrorException can be instantiated with default constructor. + /// + /// A representing the asynchronous operation. + [Test] + public async Task UnhandledErrorException_DefaultConstructor_HasDefaultMessage() + { + // Act + var fixture = new UnhandledErrorException(); + + // Assert + await Assert.That(fixture.Message) + .IsEqualTo("Exception of type 'ReactiveUI.UnhandledErrorException' was thrown."); + } + + /// + /// Tests that UnhandledErrorException can be instantiated with message and inner exception. + /// + /// A representing the asynchronous operation. + [Test] + public async Task UnhandledErrorException_MessageAndInnerException_HasBoth() + { + // Arrange + const string expectedMessage = "We are terribly sorry but a unhandled error occured."; + const string innerMessage = "Inner Exception added."; + var innerException = new Exception(innerMessage); + + // Act + var fixture = new UnhandledErrorException(expectedMessage, innerException); + + using (Assert.Multiple()) + { + // Assert + await Assert.That(fixture.Message).IsEqualTo(expectedMessage); + await Assert.That(fixture.InnerException).IsNotNull(); + await Assert.That(fixture.InnerException?.Message).IsEqualTo(innerMessage); + } + } + + /// + /// Tests that UnhandledErrorException can be instantiated with custom message. + /// + /// A representing the asynchronous operation. + [Test] + public async Task UnhandledErrorException_MessageConstructor_HasCustomMessage() + { + // Arrange + const string expectedMessage = "We are terribly sorry but a unhandled error occured."; + + // Act + var fixture = new UnhandledErrorException(expectedMessage); + + // Assert + await Assert.That(fixture.Message).IsEqualTo(expectedMessage); + } + + /// + /// Tests that ViewLocatorNotFoundException can be instantiated with default constructor. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ViewLocatorNotFoundException_DefaultConstructor_HasDefaultMessage() + { + // Act + var fixture = new ViewLocatorNotFoundException(); + + // Assert + await Assert.That(fixture.Message) + .IsEqualTo("Exception of type 'ReactiveUI.ViewLocatorNotFoundException' was thrown."); + } + + /// + /// Tests that ViewLocatorNotFoundException can be instantiated with message and inner exception. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ViewLocatorNotFoundException_MessageAndInnerException_HasBoth() + { + // Arrange + const string expectedMessage = "We are terribly sorry but the View Locator was Not Found."; + const string innerMessage = "Inner Exception added."; + var innerException = new Exception(innerMessage); + + // Act + var fixture = new ViewLocatorNotFoundException(expectedMessage, innerException); + + using (Assert.Multiple()) + { + // Assert + await Assert.That(fixture.Message).IsEqualTo(expectedMessage); + await Assert.That(fixture.InnerException).IsNotNull(); + await Assert.That(fixture.InnerException?.Message).IsEqualTo(innerMessage); + } + } + + /// + /// Tests that ViewLocatorNotFoundException can be instantiated with custom message. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ViewLocatorNotFoundException_MessageConstructor_HasCustomMessage() + { + // Arrange + const string expectedMessage = "We are terribly sorry but the View Locator was Not Found."; + + // Act + var fixture = new ViewLocatorNotFoundException(expectedMessage); + + // Assert + await Assert.That(fixture.Message).IsEqualTo(expectedMessage); + } +} diff --git a/src/tests/ReactiveUI.Tests/Core/InternalUtilitiesTests.cs b/src/tests/ReactiveUI.Tests/Core/InternalUtilitiesTests.cs new file mode 100644 index 0000000000..cc2a1d13cf --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Core/InternalUtilitiesTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2025 .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.Core; + +/// +/// Tests for ReactiveUI internal utility classes. +/// +public class InternalUtilitiesTests +{ + /// + /// Tests that NotAWeakReference always holds strong reference even after GC. + /// + /// A representing the asynchronous operation. + [Test] + public async Task NotAWeakReference_AfterGC_StillAlive() + { + // Arrange + const string target = "test target"; + var weakRef = new NotAWeakReference(target); + + // Act - Force garbage collection + GC.Collect(); + GC.WaitForPendingFinalizers(); + + using (Assert.Multiple()) + { + // Assert - NotAWeakReference always holds strong reference + await Assert.That(weakRef.IsAlive).IsTrue(); + await Assert.That(weakRef.Target).IsEqualTo(target); + } + } + + /// + /// Tests NotAWeakReference Target and IsAlive properties. + /// + /// A representing the asynchronous operation. + [Test] + public async Task NotAWeakReference_Constructor_StoresTarget() + { + // Arrange + const string target = "test target"; + + // Act + var weakRef = new NotAWeakReference(target); + + using (Assert.Multiple()) + { + // Assert + await Assert.That(weakRef.Target).IsEqualTo(target); + await Assert.That(weakRef.IsAlive).IsTrue(); + } + } +} diff --git a/src/tests/ReactiveUI.Tests/Core/ObservablesTests.cs b/src/tests/ReactiveUI.Tests/Core/ObservablesTests.cs new file mode 100644 index 0000000000..85ae2d9d53 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Core/ObservablesTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) 2025 .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.Core; + +/// +/// Tests for Observables static utility class. +/// +public class ObservablesTests +{ + /// + /// Tests that Observables.False emits false value. + /// + /// A representing the asynchronous operation. + [Test] + public async Task Observables_False_EmitsFalse() + { + // Arrange + bool? result = null; + + // Act + Observables.False.Subscribe(x => result = x); + + // Assert + await Assert.That(result).IsFalse(); + } + + /// + /// Tests that Observables static members are accessible. + /// + /// A representing the asynchronous operation. + [Test] + public async Task Observables_StaticMembers_AreAccessible() + { + using (Assert.Multiple()) + { + // Act & Assert + await Assert.That(Observables.Unit).IsNotNull(); + await Assert.That(Observables.True).IsNotNull(); + await Assert.That(Observables.False).IsNotNull(); + } + } + + /// + /// Tests that Observables.True emits true value. + /// + /// A representing the asynchronous operation. + [Test] + public async Task Observables_True_EmitsTrue() + { + // Arrange + bool? result = null; + + // Act + Observables.True.Subscribe(x => result = x); + + // Assert + await Assert.That(result).IsTrue(); + } + + /// + /// Tests that Observables.Unit emits Unit.Default value. + /// + /// A representing the asynchronous operation. + [Test] + public async Task Observables_Unit_EmitsUnitDefault() + { + // Arrange + Unit? result = null; + + // Act + Observables.Unit.Subscribe(x => result = x); + + // Assert + await Assert.That(result).IsEqualTo(Unit.Default); + } +} diff --git a/src/tests/ReactiveUI.Tests/Core/SingletonTests.cs b/src/tests/ReactiveUI.Tests/Core/SingletonTests.cs new file mode 100644 index 0000000000..56ca0c4a90 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Core/SingletonTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2025 .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.Core; + +/// +/// Tests for ReactiveUI singleton classes. +/// +public class SingletonTests +{ + /// + /// Tests SingletonDataErrorsChangedEventArgs.Value static property. + /// + /// A representing the asynchronous operation. + [Test] + public async Task SingletonDataErrorsChangedEventArgs_Value_HasCorrectPropertyName() + { + // Act & Assert + await Assert.That(SingletonDataErrorsChangedEventArgs.Value).IsNotNull(); + await Assert.That(SingletonDataErrorsChangedEventArgs.Value.PropertyName).IsEqualTo("Value"); + } + + /// + /// Tests SingletonPropertyChangedEventArgs.ErrorMessage static property. + /// + /// A representing the asynchronous operation. + [Test] + public async Task SingletonPropertyChangedEventArgs_ErrorMessage_HasCorrectPropertyName() + { + // Act & Assert + await Assert.That(SingletonPropertyChangedEventArgs.ErrorMessage).IsNotNull(); + await Assert.That(SingletonPropertyChangedEventArgs.ErrorMessage.PropertyName).IsEqualTo("ErrorMessage"); + } + + /// + /// Tests SingletonPropertyChangedEventArgs.HasErrors static property. + /// + /// A representing the asynchronous operation. + [Test] + public async Task SingletonPropertyChangedEventArgs_HasErrors_HasCorrectPropertyName() + { + // Act & Assert + await Assert.That(SingletonPropertyChangedEventArgs.HasErrors).IsNotNull(); + await Assert.That(SingletonPropertyChangedEventArgs.HasErrors.PropertyName).IsEqualTo("HasErrors"); + } + + /// + /// Tests SingletonPropertyChangedEventArgs.Value static property. + /// + /// A representing the asynchronous operation. + [Test] + public async Task SingletonPropertyChangedEventArgs_Value_HasCorrectPropertyName() + { + // Act & Assert + await Assert.That(SingletonPropertyChangedEventArgs.Value).IsNotNull(); + await Assert.That(SingletonPropertyChangedEventArgs.Value.PropertyName).IsEqualTo("Value"); + } +} diff --git a/src/tests/ReactiveUI.Tests/Expression/ExpressionMixinsTests.cs b/src/tests/ReactiveUI.Tests/Expression/ExpressionMixinsTests.cs new file mode 100644 index 0000000000..1e7a97dd6d --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Expression/ExpressionMixinsTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2025 .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.ReactiveObjects.Mocks; + +namespace ReactiveUI.Tests.Expression; + +/// +/// Tests for ExpressionMixins utility methods. +/// +public class ExpressionMixinsTests +{ + /// + /// Tests that GetMemberInfo handles nested property access. + /// + /// A representing the asynchronous operation. + [Test] + public async Task GetMemberInfo_NestedPropertyExpression_ReturnsPropertyName() + { + // Arrange + Expression> expression = x => x.IsNotNullString!.Length; + + // Act + var memberInfo = expression.Body.GetMemberInfo(); + + // Assert + await Assert.That(memberInfo).IsNotNull(); + await Assert.That(memberInfo!.Name).IsEqualTo("Length"); + } + + /// + /// Tests that GetMemberInfo returns correct member name. + /// + /// A representing the asynchronous operation. + [Test] + public async Task GetMemberInfo_PropertyExpression_ReturnsPropertyName() + { + // Arrange + Expression> expression = x => x.IsNotNullString; + + // Act + var memberInfo = expression.Body.GetMemberInfo(); + + // Assert + await Assert.That(memberInfo).IsNotNull(); + await Assert.That(memberInfo!.Name).IsEqualTo("IsNotNullString"); + } +} diff --git a/src/tests/ReactiveUI.Tests/Expression/ExpressionRewriterTests.cs b/src/tests/ReactiveUI.Tests/Expression/ExpressionRewriterTests.cs index 2f876034ec..7be446725b 100644 --- a/src/tests/ReactiveUI.Tests/Expression/ExpressionRewriterTests.cs +++ b/src/tests/ReactiveUI.Tests/Expression/ExpressionRewriterTests.cs @@ -8,33 +8,35 @@ namespace ReactiveUI.Tests.Expression; public class ExpressionRewriterTests { [Test] - public async Task Rewrite_WithParameterExpression_ReturnsParameterExpression() + public async Task Rewrite_WithArrayIndex_ReturnsIndexExpression() { - Expression> expr = x => x; + Expression> expr = x => x.Array[0]; var result = Reflection.Rewrite(expr.Body); - await Assert.That(result.NodeType).IsEqualTo(ExpressionType.Parameter); + await Assert.That(result.NodeType).IsEqualTo(ExpressionType.Index); } [Test] - public async Task Rewrite_WithMemberAccess_ReturnsMemberExpression() + public void Rewrite_WithArrayIndexNonConstant_Throws() { - Expression> expr = x => x.Property; - - var result = Reflection.Rewrite(expr.Body); + var index = 0; + Expression> expr = x => x.Array[index]; - await Assert.That(result.NodeType).IsEqualTo(ExpressionType.MemberAccess); + Assert.Throws(() => Reflection.Rewrite(expr.Body)); } [Test] - public async Task Rewrite_WithNestedMemberAccess_ReturnsMemberExpression() + public async Task Rewrite_WithArrayLength_ReturnsMemberAccess() { - Expression> expr = x => x.Nested!.Property; + Expression> expr = x => x.Array.Length; var result = Reflection.Rewrite(expr.Body); + // ArrayLength should be rewritten to MemberAccess of Length property await Assert.That(result.NodeType).IsEqualTo(ExpressionType.MemberAccess); + var memberExpr = (MemberExpression)result; + await Assert.That(memberExpr.Member.Name).IsEqualTo("Length"); } [Test] @@ -59,9 +61,9 @@ public async Task Rewrite_WithConvert_ReturnsUnderlyingExpression() } [Test] - public async Task Rewrite_WithArrayIndex_ReturnsIndexExpression() + public async Task Rewrite_WithIndexExpression_ValidatesConstantArguments() { - Expression> expr = x => x.Array[0]; + Expression> expr = x => x.List[1]; var result = Reflection.Rewrite(expr.Body); @@ -69,12 +71,16 @@ public async Task Rewrite_WithArrayIndex_ReturnsIndexExpression() } [Test] - public void Rewrite_WithArrayIndexNonConstant_Throws() + public void Rewrite_WithIndexExpressionNonConstantArguments_Throws() { - var index = 0; - Expression> expr = x => x.Array[index]; + // Create an IndexExpression with non-constant arguments + var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); + var listProperty = System.Linq.Expressions.Expression.Property(parameter, "List"); + var indexer = typeof(List).GetProperty("Item")!; + var nonConstantArg = System.Linq.Expressions.Expression.Parameter(typeof(int), "index"); + var indexExpr = System.Linq.Expressions.Expression.MakeIndex(listProperty, indexer, [nonConstantArg]); - Assert.Throws(() => Reflection.Rewrite(expr.Body)); + Assert.Throws(() => Reflection.Rewrite(indexExpr)); } [Test] @@ -97,59 +103,45 @@ public void Rewrite_WithListIndexerNonConstant_Throws() } [Test] - public async Task Rewrite_WithArrayLength_ReturnsMemberAccess() + public async Task Rewrite_WithMemberAccess_ReturnsMemberExpression() { - Expression> expr = x => x.Array.Length; + Expression> expr = x => x.Property; var result = Reflection.Rewrite(expr.Body); - // ArrayLength should be rewritten to MemberAccess of Length property await Assert.That(result.NodeType).IsEqualTo(ExpressionType.MemberAccess); - var memberExpr = (MemberExpression)result; - await Assert.That(memberExpr.Member.Name).IsEqualTo("Length"); } [Test] - public async Task Rewrite_WithUnsupportedExpression_Throws() + public void Rewrite_WithMethodCallNonSpecialName_Throws() { - Expression> expr = x => x + 1; + Expression> expr = x => x.GetValue(); - var ex = Assert.Throws(() => Reflection.Rewrite(expr.Body)); - await Assert.That(ex!.Message).Contains("Unsupported expression"); - await Assert.That(ex.Message).Contains("Add"); + Assert.Throws(() => Reflection.Rewrite(expr.Body)); } [Test] - public async Task Rewrite_WithUnsupportedBinaryExpression_ThrowsWithHelpfulMessage() + public async Task Rewrite_WithNestedMemberAccess_ReturnsMemberExpression() { - Expression> expr = x => x > 5; + Expression> expr = x => x.Nested!.Property; - var ex = Assert.Throws(() => Reflection.Rewrite(expr.Body)); - await Assert.That(ex!.Message).Contains("Did you meant to use expressions"); + var result = Reflection.Rewrite(expr.Body); + + await Assert.That(result.NodeType).IsEqualTo(ExpressionType.MemberAccess); } [Test] - public void Rewrite_WithNullExpression_Throws() - { + public void Rewrite_WithNullExpression_Throws() => Assert.Throws(() => Reflection.Rewrite(null)); - } [Test] - public async Task Rewrite_WithIndexExpression_ValidatesConstantArguments() + public async Task Rewrite_WithParameterExpression_ReturnsParameterExpression() { - Expression> expr = x => x.List[1]; + Expression> expr = x => x; var result = Reflection.Rewrite(expr.Body); - await Assert.That(result.NodeType).IsEqualTo(ExpressionType.Index); - } - - [Test] - public void Rewrite_WithMethodCallNonSpecialName_Throws() - { - Expression> expr = x => x.GetValue(); - - Assert.Throws(() => Reflection.Rewrite(expr.Body)); + await Assert.That(result.NodeType).IsEqualTo(ExpressionType.Parameter); } [Test] @@ -164,27 +156,33 @@ public void Rewrite_WithUnaryExpressionNotArrayLengthOrConvert_Throws() } [Test] - public void Rewrite_WithIndexExpressionNonConstantArguments_Throws() + public async Task Rewrite_WithUnsupportedBinaryExpression_ThrowsWithHelpfulMessage() { - // Create an IndexExpression with non-constant arguments - var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); - var listProperty = System.Linq.Expressions.Expression.Property(parameter, "List"); - var indexer = typeof(List).GetProperty("Item")!; - var nonConstantArg = System.Linq.Expressions.Expression.Parameter(typeof(int), "index"); - var indexExpr = System.Linq.Expressions.Expression.MakeIndex(listProperty, indexer, [nonConstantArg]); + Expression> expr = x => x > 5; - Assert.Throws(() => Reflection.Rewrite(indexExpr)); + var ex = Assert.Throws(() => Reflection.Rewrite(expr.Body)); + await Assert.That(ex!.Message).Contains("Did you meant to use expressions"); + } + + [Test] + public async Task Rewrite_WithUnsupportedExpression_Throws() + { + Expression> expr = x => x + 1; + + var ex = Assert.Throws(() => Reflection.Rewrite(expr.Body)); + await Assert.That(ex!.Message).Contains("Unsupported expression"); + await Assert.That(ex.Message).Contains("Add"); } private class TestClass { - public string? Property { get; set; } + public int[] Array { get; } = [1, 2, 3]; - public TestClass? Nested { get; set; } + public List List { get; } = [4, 5, 6]; - public int[] Array { get; set; } = [1, 2, 3]; + public TestClass? Nested { get; set; } - public List List { get; set; } = [4, 5, 6]; + public string? Property { get; set; } public string? GetValue() => Property; } diff --git a/src/tests/ReactiveUI.Tests/Expression/ReflectionTests.cs b/src/tests/ReactiveUI.Tests/Expression/ReflectionTests.cs index c441a46919..674df42454 100644 --- a/src/tests/ReactiveUI.Tests/Expression/ReflectionTests.cs +++ b/src/tests/ReactiveUI.Tests/Expression/ReflectionTests.cs @@ -9,16 +9,6 @@ namespace ReactiveUI.Tests.Expression; public class ReflectionTests { - [Test] - public async Task ExpressionToPropertyNames_WithSimpleProperty_ReturnsPropertyName() - { - Expression> expr = x => x.Property; - - var result = Reflection.ExpressionToPropertyNames(expr.Body); - - await Assert.That(result).IsEqualTo("Property"); - } - [Test] public async Task ExpressionToPropertyNames_WithNestedProperty_ReturnsChainedNames() { @@ -30,412 +20,403 @@ public async Task ExpressionToPropertyNames_WithNestedProperty_ReturnsChainedNam } [Test] - public void ExpressionToPropertyNames_WithNull_Throws() - { + public void ExpressionToPropertyNames_WithNull_Throws() => Assert.Throws(() => Reflection.ExpressionToPropertyNames(null)); - } [Test] - public async Task GetValueFetcherForProperty_WithProperty_ReturnsFetcher() + public async Task ExpressionToPropertyNames_WithSimpleProperty_ReturnsPropertyName() { - var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; + Expression> expr = x => x.Property; - var fetcher = Reflection.GetValueFetcherForProperty(propertyInfo); + var result = Reflection.ExpressionToPropertyNames(expr.Body); - await Assert.That(fetcher).IsNotNull(); - var testObj = new TestClass { Property = "test" }; - var value = fetcher!(testObj, null); - await Assert.That(value).IsEqualTo("test"); + await Assert.That(result).IsEqualTo("Property"); } [Test] - public async Task GetValueFetcherForProperty_WithField_ReturnsFetcher() + public async Task GetArgumentsArray_WithIndexExpression_ReturnsArguments() { - var fieldInfo = typeof(TestClass).GetField(nameof(TestClass.PublicField))!; + var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); + var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); + var indexer = typeof(Dictionary).GetProperty("Item")!; + var keyArg = System.Linq.Expressions.Expression.Constant("key"); + var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); - var fetcher = Reflection.GetValueFetcherForProperty(fieldInfo); + var args = indexExpr.GetArgumentsArray(); - await Assert.That(fetcher).IsNotNull(); - var testObj = new TestClass { PublicField = 42 }; - var value = fetcher!(testObj, null); - await Assert.That(value).IsEqualTo(42); + await Assert.That(args).IsNotNull(); + await Assert.That(args!.Length).IsEqualTo(1); + await Assert.That(args[0]).IsEqualTo("key"); } [Test] - public void GetValueFetcherForProperty_WithNull_Throws() + public async Task GetArgumentsArray_WithMultiDimensionalIndex_ReturnsAllArguments() { - Assert.Throws(() => Reflection.GetValueFetcherForProperty(null)); + var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); + var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); + var key = System.Linq.Expressions.Expression.Constant("key"); + var indexExpr = System.Linq.Expressions.Expression.Property(dictProperty, "Item", key); + + var args = indexExpr.GetArgumentsArray(); + + await Assert.That(args).IsNotNull(); + await Assert.That(args!.Length).IsEqualTo(1); + await Assert.That(args[0]).IsEqualTo("key"); } [Test] - public async Task GetValueFetcherOrThrow_WithProperty_ReturnsFetcher() + public async Task GetArgumentsArray_WithNonIndexExpression_ReturnsNull() { - var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; + Expression> expr = x => x.Property; - var fetcher = Reflection.GetValueFetcherOrThrow(propertyInfo); + var args = expr.Body.GetArgumentsArray(); - await Assert.That(fetcher).IsNotNull(); + await Assert.That(args is null).IsTrue(); } [Test] - public void GetValueFetcherOrThrow_WithNull_Throws() - { - Assert.Throws(() => Reflection.GetValueFetcherOrThrow(null)); - } + public void GetEventArgsTypeForEvent_WithInvalidEvent_Throws() => Assert.Throws(() => + Reflection.GetEventArgsTypeForEvent(typeof(TestClass), "NonExistentEvent")); [Test] - public async Task GetValueSetterForProperty_WithProperty_ReturnsSetter() - { - var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; + public void GetEventArgsTypeForEvent_WithNullType_Throws() => + Assert.Throws(() => Reflection.GetEventArgsTypeForEvent(null!, "TestEvent")); - var setter = Reflection.GetValueSetterForProperty(propertyInfo); + [Test] + public async Task GetEventArgsTypeForEvent_WithValidEvent_ReturnsEventArgsType() + { + var eventArgsType = Reflection.GetEventArgsTypeForEvent(typeof(TestClass), nameof(TestClass.TestEvent)); - await Assert.That(setter).IsNotNull(); - var testObj = new TestClass(); - setter(testObj, "newValue", null); - await Assert.That(testObj.Property).IsEqualTo("newValue"); + await Assert.That(eventArgsType).IsEqualTo(typeof(EventArgs)); } [Test] - public async Task GetValueSetterForProperty_WithField_ReturnsSetter() + public async Task GetExpressionChain_WithIndexExpression_HandlesIndexer() { - var fieldInfo = typeof(TestClass).GetField(nameof(TestClass.PublicField))!; + var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); + var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); + var indexer = typeof(Dictionary).GetProperty("Item")!; + var keyArg = System.Linq.Expressions.Expression.Constant("key"); + var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); - var setter = Reflection.GetValueSetterForProperty(fieldInfo); + var chain = indexExpr.GetExpressionChain(); - await Assert.That(setter).IsNotNull(); - var testObj = new TestClass(); - setter(testObj, 99, null); - await Assert.That(testObj.PublicField).IsEqualTo(99); + await Assert.That(chain).IsNotEmpty(); + var chainList = chain.ToList(); + await Assert.That(chainList.Count).IsEqualTo(2); + await Assert.That(chainList[0].NodeType).IsEqualTo(ExpressionType.MemberAccess); + await Assert.That(chainList[1].NodeType).IsEqualTo(ExpressionType.Index); } [Test] - public void GetValueSetterForProperty_WithNull_Throws() + public async Task GetExpressionChain_WithNestedIndexExpression_HandlesChain() { - Assert.Throws(() => Reflection.GetValueSetterForProperty(null)); + var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); + var nestedProperty = System.Linq.Expressions.Expression.Property(parameter, "Nested"); + var dictProperty = System.Linq.Expressions.Expression.Property(nestedProperty, "Dictionary"); + var indexer = typeof(Dictionary).GetProperty("Item")!; + var keyArg = System.Linq.Expressions.Expression.Constant("key"); + var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); + + var chain = indexExpr.GetExpressionChain(); + + await Assert.That(chain).IsNotEmpty(); + var chainList = chain.ToList(); + await Assert.That(chainList.Count).IsEqualTo(3); } [Test] - public async Task GetValueSetterOrThrow_WithProperty_ReturnsSetter() + public async Task GetMemberInfo_WithConvertCheckedExpression_ReturnsUnderlyingMember() { - var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; + var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); + var member = System.Linq.Expressions.Expression.Field(parameter, "PublicField"); + var convertChecked = System.Linq.Expressions.Expression.ConvertChecked(member, typeof(long)); - var setter = Reflection.GetValueSetterOrThrow(propertyInfo); + var memberInfo = convertChecked.GetMemberInfo(); - await Assert.That(setter).IsNotNull(); + await Assert.That(memberInfo).IsNotNull(); + await Assert.That(memberInfo!.Name).IsEqualTo("PublicField"); } [Test] - public void GetValueSetterOrThrow_WithNull_Throws() + public async Task GetMemberInfo_WithConvertExpression_ReturnsUnderlyingMember() { - Assert.Throws(() => Reflection.GetValueSetterOrThrow(null)); + Expression> expr = x => x.Property!; + + var memberInfo = expr.Body.GetMemberInfo(); + + await Assert.That(memberInfo).IsNotNull(); + await Assert.That(memberInfo!.Name).IsEqualTo("Property"); } [Test] - public async Task TryGetValueForPropertyChain_WithValidChain_GetsValue() + public async Task GetMemberInfo_WithIndexExpression_ReturnsIndexer() { - var obj = new TestClass - { - Nested = new TestClass { Property = "nestedValue" } - }; - Expression> expr = x => x.Nested!.Property; - var chain = expr.Body.GetExpressionChain(); + var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); + var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); + var indexer = typeof(Dictionary).GetProperty("Item")!; + var keyArg = System.Linq.Expressions.Expression.Constant("key"); + var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); - var result = Reflection.TryGetValueForPropertyChain(out var value, obj, chain); + var memberInfo = indexExpr.GetMemberInfo(); - await Assert.That(result).IsTrue(); - await Assert.That(value).IsEqualTo("nestedValue"); + await Assert.That(memberInfo).IsNotNull(); + await Assert.That(memberInfo).IsTypeOf(); } [Test] - public async Task TryGetValueForPropertyChain_WithNullInChain_ReturnsFalse() + public void GetMemberInfo_WithUnsupportedExpression_Throws() { - var obj = new TestClass { Nested = null }; - Expression> expr = x => x.Nested!.Property; - var chain = expr.Body.GetExpressionChain(); - - var result = Reflection.TryGetValueForPropertyChain(out var value, obj, chain); + var constant = System.Linq.Expressions.Expression.Constant(42); - await Assert.That(result).IsFalse(); - await Assert.That(value).IsNull(); + Assert.Throws(() => constant.GetMemberInfo()); } [Test] - public async Task TrySetValueToPropertyChain_WithValidChain_SetsValue() + public async Task GetParent_WithIndexExpression_ReturnsObject() { - var obj = new TestClass - { - Nested = new TestClass() - }; - Expression> expr = x => x.Nested!.Property; - var chain = expr.Body.GetExpressionChain(); + var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); + var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); + var indexer = typeof(Dictionary).GetProperty("Item")!; + var keyArg = System.Linq.Expressions.Expression.Constant("key"); + var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); - var result = Reflection.TrySetValueToPropertyChain(obj, chain, "setValue"); + var parent = indexExpr.GetParent(); - await Assert.That(result).IsTrue(); - await Assert.That(obj.Nested!.Property).IsEqualTo("setValue"); + await Assert.That(parent).IsNotNull(); + await Assert.That(parent!.NodeType).IsEqualTo(ExpressionType.MemberAccess); } [Test] - public async Task TrySetValueToPropertyChain_WithNullTarget_ReturnsFalse() + public async Task GetParent_WithMemberExpression_ReturnsExpression() { - var obj = new TestClass { Nested = null }; Expression> expr = x => x.Nested!.Property; - var chain = expr.Body.GetExpressionChain(); + var memberExpr = (MemberExpression)expr.Body; - var result = Reflection.TrySetValueToPropertyChain(obj, chain, "setValue", shouldThrow: false); + var parent = memberExpr.GetParent(); - await Assert.That(result).IsFalse(); + await Assert.That(parent).IsNotNull(); + await Assert.That(parent!.NodeType).IsEqualTo(ExpressionType.MemberAccess); } [Test] - public async Task ReallyFindType_WithValidTypeName_ReturnsType() + public void GetParent_WithUnsupportedExpression_Throws() { - var typeName = typeof(TestClass).AssemblyQualifiedName!; - - var result = Reflection.ReallyFindType(typeName, false); + var constant = System.Linq.Expressions.Expression.Constant(42); - await Assert.That(result).IsNotNull(); - await Assert.That(result).IsEqualTo(typeof(TestClass)); + Assert.Throws(() => constant.GetParent()); } [Test] - public async Task ReallyFindType_WithInvalidTypeName_ReturnsNull() + public async Task GetValueFetcherForProperty_WithField_ReturnsFetcher() { - var result = Reflection.ReallyFindType("Invalid.Type.Name", false); + var fieldInfo = typeof(TestClass).GetField(nameof(TestClass.PublicField))!; - await Assert.That(result).IsNull(); + var fetcher = Reflection.GetValueFetcherForProperty(fieldInfo); + + await Assert.That(fetcher).IsNotNull(); + var testObj = new TestClass { PublicField = 42 }; + var value = fetcher!(testObj, null); + await Assert.That(value).IsEqualTo(42); } [Test] - public void ReallyFindType_WithInvalidTypeNameAndThrow_Throws() - { - Assert.Throws(() => Reflection.ReallyFindType("Invalid.Type.Name", true)); - } + public void GetValueFetcherForProperty_WithNull_Throws() => + Assert.Throws(() => Reflection.GetValueFetcherForProperty(null)); [Test] - public async Task GetEventArgsTypeForEvent_WithValidEvent_ReturnsEventArgsType() + public async Task GetValueFetcherForProperty_WithProperty_ReturnsFetcher() { - var eventArgsType = Reflection.GetEventArgsTypeForEvent(typeof(TestClass), nameof(TestClass.TestEvent)); + var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; - await Assert.That(eventArgsType).IsEqualTo(typeof(EventArgs)); - } + var fetcher = Reflection.GetValueFetcherForProperty(propertyInfo); - [Test] - public void GetEventArgsTypeForEvent_WithInvalidEvent_Throws() - { - Assert.Throws(() => Reflection.GetEventArgsTypeForEvent(typeof(TestClass), "NonExistentEvent")); + await Assert.That(fetcher).IsNotNull(); + var testObj = new TestClass { Property = "test" }; + var value = fetcher!(testObj, null); + await Assert.That(value).IsEqualTo("test"); } [Test] - public void GetEventArgsTypeForEvent_WithNullType_Throws() - { - Assert.Throws(() => Reflection.GetEventArgsTypeForEvent(null!, "TestEvent")); - } + public void GetValueFetcherOrThrow_WithNull_Throws() => + Assert.Throws(() => Reflection.GetValueFetcherOrThrow(null)); [Test] - public async Task IsStatic_WithStaticProperty_ReturnsTrue() + public async Task GetValueFetcherOrThrow_WithProperty_ReturnsFetcher() { - var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.StaticProperty))!; + var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; - var result = propertyInfo.IsStatic(); + var fetcher = Reflection.GetValueFetcherOrThrow(propertyInfo); - await Assert.That(result).IsTrue(); + await Assert.That(fetcher).IsNotNull(); } [Test] - public async Task IsStatic_WithInstanceProperty_ReturnsFalse() + public async Task GetValueSetterForProperty_WithField_ReturnsSetter() { - var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; + var fieldInfo = typeof(TestClass).GetField(nameof(TestClass.PublicField))!; - var result = propertyInfo.IsStatic(); + var setter = Reflection.GetValueSetterForProperty(fieldInfo); - await Assert.That(result).IsFalse(); + await Assert.That(setter).IsNotNull(); + var testObj = new TestClass(); + setter(testObj, 99, null); + await Assert.That(testObj.PublicField).IsEqualTo(99); } [Test] - public void IsStatic_WithNull_Throws() - { - PropertyInfo? propertyInfo = null; - Assert.Throws(() => propertyInfo!.IsStatic()); - } + public void GetValueSetterForProperty_WithNull_Throws() => + Assert.Throws(() => Reflection.GetValueSetterForProperty(null)); [Test] - public async Task GetExpressionChain_WithIndexExpression_HandlesIndexer() + public async Task GetValueSetterForProperty_WithProperty_ReturnsSetter() { - var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); - var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); - var indexer = typeof(Dictionary).GetProperty("Item")!; - var keyArg = System.Linq.Expressions.Expression.Constant("key"); - var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); + var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; - var chain = indexExpr.GetExpressionChain(); + var setter = Reflection.GetValueSetterForProperty(propertyInfo); - await Assert.That(chain).IsNotEmpty(); - var chainList = chain.ToList(); - await Assert.That(chainList.Count).IsEqualTo(2); - await Assert.That(chainList[0].NodeType).IsEqualTo(System.Linq.Expressions.ExpressionType.MemberAccess); - await Assert.That(chainList[1].NodeType).IsEqualTo(System.Linq.Expressions.ExpressionType.Index); + await Assert.That(setter).IsNotNull(); + var testObj = new TestClass(); + setter(testObj, "newValue", null); + await Assert.That(testObj.Property).IsEqualTo("newValue"); } [Test] - public async Task GetExpressionChain_WithNestedIndexExpression_HandlesChain() + public void GetValueSetterOrThrow_WithNull_Throws() => + Assert.Throws(() => Reflection.GetValueSetterOrThrow(null)); + + [Test] + public async Task GetValueSetterOrThrow_WithProperty_ReturnsSetter() { - var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); - var nestedProperty = System.Linq.Expressions.Expression.Property(parameter, "Nested"); - var dictProperty = System.Linq.Expressions.Expression.Property(nestedProperty, "Dictionary"); - var indexer = typeof(Dictionary).GetProperty("Item")!; - var keyArg = System.Linq.Expressions.Expression.Constant("key"); - var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); + var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; - var chain = indexExpr.GetExpressionChain(); + var setter = Reflection.GetValueSetterOrThrow(propertyInfo); - await Assert.That(chain).IsNotEmpty(); - var chainList = chain.ToList(); - await Assert.That(chainList.Count).IsEqualTo(3); + await Assert.That(setter).IsNotNull(); } [Test] - public async Task GetMemberInfo_WithIndexExpression_ReturnsIndexer() + public async Task IsStatic_WithInstanceProperty_ReturnsFalse() { - var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); - var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); - var indexer = typeof(Dictionary).GetProperty("Item")!; - var keyArg = System.Linq.Expressions.Expression.Constant("key"); - var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); + var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.Property))!; - var memberInfo = indexExpr.GetMemberInfo(); + var result = propertyInfo.IsStatic(); - await Assert.That(memberInfo).IsNotNull(); - await Assert.That(memberInfo).IsTypeOf(); + await Assert.That(result).IsFalse(); } [Test] - public async Task GetMemberInfo_WithConvertExpression_ReturnsUnderlyingMember() + public void IsStatic_WithNull_Throws() { - Expression> expr = x => (object)x.Property!; - - var memberInfo = expr.Body.GetMemberInfo(); - - await Assert.That(memberInfo).IsNotNull(); - await Assert.That(memberInfo!.Name).IsEqualTo("Property"); + PropertyInfo? propertyInfo = null; + Assert.Throws(() => propertyInfo!.IsStatic()); } [Test] - public async Task GetMemberInfo_WithConvertCheckedExpression_ReturnsUnderlyingMember() + public async Task IsStatic_WithStaticProperty_ReturnsTrue() { - var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); - var member = System.Linq.Expressions.Expression.Field(parameter, "PublicField"); - var convertChecked = System.Linq.Expressions.Expression.ConvertChecked(member, typeof(long)); + var propertyInfo = typeof(TestClass).GetProperty(nameof(TestClass.StaticProperty))!; - var memberInfo = convertChecked.GetMemberInfo(); + var result = propertyInfo.IsStatic(); - await Assert.That(memberInfo).IsNotNull(); - await Assert.That(memberInfo!.Name).IsEqualTo("PublicField"); + await Assert.That(result).IsTrue(); } [Test] - public void GetMemberInfo_WithUnsupportedExpression_Throws() + public async Task ReallyFindType_WithInvalidTypeName_ReturnsNull() { - var constant = System.Linq.Expressions.Expression.Constant(42); + var result = Reflection.ReallyFindType("Invalid.Type.Name", false); - Assert.Throws(() => constant.GetMemberInfo()); + await Assert.That(result).IsNull(); } [Test] - public async Task GetParent_WithIndexExpression_ReturnsObject() - { - var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); - var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); - var indexer = typeof(Dictionary).GetProperty("Item")!; - var keyArg = System.Linq.Expressions.Expression.Constant("key"); - var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); - - var parent = indexExpr.GetParent(); - - await Assert.That(parent).IsNotNull(); - await Assert.That(parent!.NodeType).IsEqualTo(System.Linq.Expressions.ExpressionType.MemberAccess); - } + public void ReallyFindType_WithInvalidTypeNameAndThrow_Throws() => + Assert.Throws(() => Reflection.ReallyFindType("Invalid.Type.Name", true)); [Test] - public async Task GetParent_WithMemberExpression_ReturnsExpression() + public async Task ReallyFindType_WithValidTypeName_ReturnsType() { - Expression> expr = x => x.Nested!.Property; - var memberExpr = (MemberExpression)expr.Body; + var typeName = typeof(TestClass).AssemblyQualifiedName!; - var parent = memberExpr.GetParent(); + var result = Reflection.ReallyFindType(typeName, false); - await Assert.That(parent).IsNotNull(); - await Assert.That(parent!.NodeType).IsEqualTo(System.Linq.Expressions.ExpressionType.MemberAccess); + await Assert.That(result).IsNotNull(); + await Assert.That(result).IsEqualTo(typeof(TestClass)); } [Test] - public void GetParent_WithUnsupportedExpression_Throws() + public async Task TryGetValueForPropertyChain_WithNullInChain_ReturnsFalse() { - var constant = System.Linq.Expressions.Expression.Constant(42); + var obj = new TestClass { Nested = null }; + Expression> expr = x => x.Nested!.Property; + var chain = expr.Body.GetExpressionChain(); - Assert.Throws(() => constant.GetParent()); + var result = Reflection.TryGetValueForPropertyChain(out var value, obj, chain); + + await Assert.That(result).IsFalse(); + await Assert.That(value).IsNull(); } [Test] - public async Task GetArgumentsArray_WithIndexExpression_ReturnsArguments() + public async Task TryGetValueForPropertyChain_WithValidChain_GetsValue() { - var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); - var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); - var indexer = typeof(Dictionary).GetProperty("Item")!; - var keyArg = System.Linq.Expressions.Expression.Constant("key"); - var indexExpr = System.Linq.Expressions.Expression.MakeIndex(dictProperty, indexer, [keyArg]); + var obj = new TestClass { Nested = new TestClass { Property = "nestedValue" } }; + Expression> expr = x => x.Nested!.Property; + var chain = expr.Body.GetExpressionChain(); - var args = indexExpr.GetArgumentsArray(); + var result = Reflection.TryGetValueForPropertyChain(out var value, obj, chain); - await Assert.That(args).IsNotNull(); - await Assert.That(args!.Length).IsEqualTo(1); - await Assert.That(args[0]).IsEqualTo("key"); + await Assert.That(result).IsTrue(); + await Assert.That(value).IsEqualTo("nestedValue"); } [Test] - public async Task GetArgumentsArray_WithMultiDimensionalIndex_ReturnsAllArguments() + public async Task TrySetValueToPropertyChain_WithNullTarget_ReturnsFalse() { - var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TestClass), "x"); - var dictProperty = System.Linq.Expressions.Expression.Property(parameter, "Dictionary"); - var key = System.Linq.Expressions.Expression.Constant("key"); - var indexExpr = System.Linq.Expressions.Expression.Property(dictProperty, "Item", key); + var obj = new TestClass { Nested = null }; + Expression> expr = x => x.Nested!.Property; + var chain = expr.Body.GetExpressionChain(); - var args = indexExpr.GetArgumentsArray(); + var result = Reflection.TrySetValueToPropertyChain(obj, chain, "setValue", false); - await Assert.That(args).IsNotNull(); - await Assert.That(args!.Length).IsEqualTo(1); - await Assert.That(args[0]).IsEqualTo("key"); + await Assert.That(result).IsFalse(); } [Test] - public async Task GetArgumentsArray_WithNonIndexExpression_ReturnsNull() + public async Task TrySetValueToPropertyChain_WithValidChain_SetsValue() { - Expression> expr = x => x.Property; + var obj = new TestClass { Nested = new TestClass() }; + Expression> expr = x => x.Nested!.Property; + var chain = expr.Body.GetExpressionChain(); - var args = expr.Body.GetArgumentsArray(); + var result = Reflection.TrySetValueToPropertyChain(obj, chain, "setValue"); - await Assert.That(args).IsNull(); + await Assert.That(result).IsTrue(); + await Assert.That(obj.Nested!.Property).IsEqualTo("setValue"); } public class TestClass { - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Public field required for reflection tests")] + [SuppressMessage( + "StyleCop.CSharp.MaintainabilityRules", + "SA1401:Fields should be private", + Justification = "Public field required for reflection tests")] public int PublicField; public event EventHandler? TestEvent; public static string? StaticProperty { get; set; } - public string? Property { get; set; } - - public TestClass? Nested { get; set; } - public int[] Array { get; set; } = [1, 2, 3]; + public Dictionary Dictionary { get; set; } = new() { { "key", 42 } }; + public List List { get; set; } = [1, 2, 3]; - public Dictionary Dictionary { get; set; } = new Dictionary { { "key", 42 } }; + public TestClass? Nested { get; set; } + + public string? Property { get; set; } public void RaiseTestEvent() => TestEvent?.Invoke(this, EventArgs.Empty); } diff --git a/src/tests/ReactiveUI.Tests/IROObservableForPropertyTest.cs b/src/tests/ReactiveUI.Tests/IROObservableForPropertyTest.cs index 305e26ff73..7240a341c9 100644 --- a/src/tests/ReactiveUI.Tests/IROObservableForPropertyTest.cs +++ b/src/tests/ReactiveUI.Tests/IROObservableForPropertyTest.cs @@ -5,17 +5,15 @@ namespace ReactiveUI.Tests; -using Expression = System.Linq.Expressions.Expression; - /// -/// Tests for . +/// Tests for . /// public class IROObservableForPropertyTest { /// - /// Tests that GetAffinityForObject returns 10 for IReactiveObject types. + /// Tests that GetAffinityForObject returns 10 for IReactiveObject types. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task GetAffinityForObject_IReactiveObjectType_Returns10() { @@ -27,9 +25,9 @@ public async Task GetAffinityForObject_IReactiveObjectType_Returns10() } /// - /// Tests that GetAffinityForObject returns 0 for non-IReactiveObject types. + /// Tests that GetAffinityForObject returns 0 for non-IReactiveObject types. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task GetAffinityForObject_NonReactiveObjectType_Returns0() { @@ -41,23 +39,23 @@ public async Task GetAffinityForObject_NonReactiveObjectType_Returns0() } /// - /// Tests that GetAffinityForObject returns 10 regardless of beforeChanged parameter. + /// Tests that GetAffinityForObject returns 10 regardless of beforeChanged parameter. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task GetAffinityForObject_WithBeforeChanged_Returns10() { var oaph = new IROObservableForProperty(); - var affinity = oaph.GetAffinityForObject(typeof(TestReactiveObject), "TestProperty", beforeChanged: true); + var affinity = oaph.GetAffinityForObject(typeof(TestReactiveObject), "TestProperty", true); await Assert.That(affinity).IsEqualTo(10); } /// - /// Tests that GetNotificationForProperty throws for non-IReactiveObject sender. + /// Tests that GetNotificationForProperty throws for non-IReactiveObject sender. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task GetNotificationForProperty_NonReactiveObjectSender_Throws() { @@ -70,9 +68,9 @@ await Assert.That(() => oaph.GetNotificationForProperty(sender, expression.Body, } /// - /// Tests that GetNotificationForProperty throws for null expression. + /// Tests that GetNotificationForProperty throws for null expression. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task GetNotificationForProperty_NullExpression_Throws() { @@ -84,47 +82,47 @@ await Assert.That(() => oaph.GetNotificationForProperty(sender, null!, "TestProp } /// - /// Tests that GetNotificationForProperty returns observable for property changes. + /// Tests that GetNotificationForProperty emits when property changes. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetNotificationForProperty_ValidSender_ReturnsObservable() + public async Task GetNotificationForProperty_PropertyChanges_EmitsNotification() { var oaph = new IROObservableForProperty(); var sender = new TestReactiveObject(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestReactiveObject), "x"); var expression = System.Linq.Expressions.Expression.Property(param, nameof(TestReactiveObject.TestProperty)); - var observable = oaph.GetNotificationForProperty(sender, expression, nameof(TestReactiveObject.TestProperty)); + var changes = new List>(); + oaph.GetNotificationForProperty(sender, expression, nameof(TestReactiveObject.TestProperty)) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(changes.Add); - await Assert.That(observable).IsNotNull(); + sender.TestProperty = "value1"; + sender.TestProperty = "value2"; + + await Assert.That(changes).Count().IsEqualTo(2); } /// - /// Tests that GetNotificationForProperty emits when property changes. + /// Tests that GetNotificationForProperty returns observable for property changes. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetNotificationForProperty_PropertyChanges_EmitsNotification() + public async Task GetNotificationForProperty_ValidSender_ReturnsObservable() { var oaph = new IROObservableForProperty(); var sender = new TestReactiveObject(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestReactiveObject), "x"); var expression = System.Linq.Expressions.Expression.Property(param, nameof(TestReactiveObject.TestProperty)); - var changes = new List>(); - oaph.GetNotificationForProperty(sender, expression, nameof(TestReactiveObject.TestProperty)) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(changes.Add); - - sender.TestProperty = "value1"; - sender.TestProperty = "value2"; + var observable = oaph.GetNotificationForProperty(sender, expression, nameof(TestReactiveObject.TestProperty)); - await Assert.That(changes).Count().IsEqualTo(2); + await Assert.That(observable).IsNotNull(); } /// - /// Test reactive object for testing. + /// Test reactive object for testing. /// private class TestReactiveObject : ReactiveObject { diff --git a/src/tests/ReactiveUI.Tests/Infrastructure/RxAppTestExtensions.cs b/src/tests/ReactiveUI.Tests/Infrastructure/RxAppTestExtensions.cs new file mode 100644 index 0000000000..0771ca1968 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Infrastructure/RxAppTestExtensions.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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; + +namespace ReactiveUI.Tests.Infrastructure; + +/// +/// Extension methods for testing ReactiveUI initialization and reset. +/// +internal static class RxAppTestExtensions +{ + /// + /// Resets ReactiveUI state and reinitializes with core services using a fresh locator. + /// + /// + /// This method: + /// 1. Resets the ReactiveUI initialization state. + /// 2. Creates a new ModernDependencyResolver. + /// 3. Initializes Splat with it. + /// 4. Sets it as the current locator. + /// 5. Initializes ReactiveUI with core services. + /// + public static void ResetAndReinitialize() + { + // Reset the initialization flag + RxAppBuilder.ResetForTesting(); + + // Create a fresh dependency resolver + var resolver = new ModernDependencyResolver(); + resolver.InitializeSplat(); + + // Set it as the current locator + AppLocator.SetLocator(resolver); + + // Initialize ReactiveUI with core services + RxAppBuilder.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); + } + + /// + /// Resets ReactiveUI state only (does not reinitialize). + /// + /// + /// Use this when you want to manually control the initialization afterward, + /// such as when creating a custom resolver for a specific test. + /// + public static void ResetState() => RxAppBuilder.ResetForTesting(); +} diff --git a/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/LocatorScope.cs b/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/LocatorScope.cs deleted file mode 100644 index 69ba29186e..0000000000 --- a/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/LocatorScope.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2025 .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 Splat; - -namespace ReactiveUI.Tests.Infrastructure.StaticState; - -/// -/// A disposable scope that snapshots and restores Splat's Locator.Current static state. -/// Use this in test fixtures that read or modify Locator.CurrentMutable to ensure -/// static state is properly restored after tests complete. -/// -/// -/// This helper is necessary because Splat's Locator maintains a static/global reference -/// that can leak between test executions, causing intermittent failures. -/// Tests using this scope should also be marked with [NotInParallel] to prevent -/// concurrent modifications to the shared state. -/// -/// -/// -/// [NotInParallel] -/// public class MyTests -/// { -/// private LocatorScope? _locatorScope; -/// -/// [Before(Test)] -/// public void SetUp() -/// { -/// _locatorScope = new LocatorScope(); -/// // Now safe to use Locator.CurrentMutable -/// } -/// -/// [After(Test)] -/// public void TearDown() -/// { -/// _locatorScope?.Dispose(); -/// } -/// } -/// -/// -public sealed class LocatorScope : IDisposable -{ - private readonly IReadonlyDependencyResolver _previousLocator; - - /// - /// Initializes a new instance of the class. - /// Captures the current Locator state and sets up a fresh locator for testing. - /// - public LocatorScope() - { - // Save the current locator so we can restore it later - _previousLocator = Locator.Current; - - // Replace with a new locator that tests can modify - var newLocator = new ModernDependencyResolver(); - newLocator.InitializeSplat(); - newLocator.InitializeReactiveUI(); - Locator.SetLocator(newLocator); - } - - /// - /// Restores the Locator to its previous state. - /// - public void Dispose() - { - // Restore the previous locator - // Cast is safe because we saved it from Locator.Current - Locator.SetLocator((IDependencyResolver)_previousLocator); - } -} diff --git a/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/MessageBusScope.cs b/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/MessageBusScope.cs deleted file mode 100644 index decfa8218a..0000000000 --- a/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/MessageBusScope.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2025 .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.Infrastructure.StaticState; - -/// -/// A disposable scope that snapshots and restores MessageBus.Current static state. -/// Use this in test fixtures that read or modify MessageBus.Current to ensure -/// static state is properly restored after tests complete. -/// -/// -/// This helper is necessary because MessageBus.Current maintains a static/global reference -/// that can leak between parallel test executions, causing intermittent failures. -/// Tests using this scope should also be marked with [NotInParallel] to prevent -/// concurrent modifications to the shared state. -/// -/// -/// -/// [TestFixture] -/// [NotInParallel] -/// public class MyTests -/// { -/// private MessageBusScope _messageBusScope; -/// -/// [SetUp] -/// public void SetUp() -/// { -/// _messageBusScope = new MessageBusScope(); -/// // Now safe to use MessageBus.Current -/// } -/// -/// [TearDown] -/// public void TearDown() -/// { -/// _messageBusScope?.Dispose(); -/// } -/// } -/// -/// -public sealed class MessageBusScope : IDisposable -{ - private readonly IMessageBus _previousMessageBus; - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// Snapshots the current MessageBus.Current state. - /// - public MessageBusScope() - { - _previousMessageBus = MessageBus.Current; - } - - /// - /// Restores the MessageBus.Current state to what it was when this scope was created. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - MessageBus.Current = _previousMessageBus; - _disposed = true; - } -} diff --git a/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/RxAppSchedulersScope.cs b/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/RxAppSchedulersScope.cs deleted file mode 100644 index 038d407562..0000000000 --- a/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/RxAppSchedulersScope.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2025 .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.Infrastructure.StaticState; - -/// -/// A disposable scope that snapshots and restores RxApp scheduler state. -/// Use this in test fixtures that modify RxApp.MainThreadScheduler or RxApp.TaskpoolScheduler -/// to ensure static state is properly restored after tests complete. -/// -/// -/// This helper is necessary because RxApp maintains static/global scheduler references -/// that can leak between parallel test executions, causing intermittent failures. -/// Tests using this scope should also be marked with [NotInParallel] to prevent -/// concurrent modifications to the shared state. -/// -/// -/// -/// [TestFixture] -/// [NotInParallel] -/// public class MyTests -/// { -/// private RxAppSchedulersScope _schedulersScope; -/// -/// [SetUp] -/// public void SetUp() -/// { -/// _schedulersScope = new RxAppSchedulersScope(); -/// // Now safe to modify RxApp schedulers -/// } -/// -/// [TearDown] -/// public void TearDown() -/// { -/// _schedulersScope?.Dispose(); -/// } -/// } -/// -/// -public sealed class RxAppSchedulersScope : IDisposable -{ - private readonly IScheduler _mainThreadScheduler; - private readonly IScheduler _taskpoolScheduler; - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// Snapshots the current RxApp scheduler state. - /// - public RxAppSchedulersScope() - { - _mainThreadScheduler = RxApp.MainThreadScheduler; - _taskpoolScheduler = RxApp.TaskpoolScheduler; - } - - /// - /// Restores the RxApp scheduler state to what it was when this scope was created. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - RxApp.MainThreadScheduler = _mainThreadScheduler; - RxApp.TaskpoolScheduler = _taskpoolScheduler; - _disposed = true; - } -} diff --git a/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/StaticStateScope.cs b/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/StaticStateScope.cs deleted file mode 100644 index 6dc8954a35..0000000000 --- a/src/tests/ReactiveUI.Tests/Infrastructure/StaticState/StaticStateScope.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2025 .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.Infrastructure.StaticState; - -/// -/// A generic disposable scope for capturing and restoring arbitrary static state. -/// Use this when you need to snapshot and restore state that doesn't have a dedicated scope class. -/// -/// -/// This helper allows you to capture any state via getter functions and restore it via setter actions. -/// Tests using this scope should also be marked with [NotInParallel] to prevent -/// concurrent modifications to the shared state. -/// -/// -/// -/// [TestFixture] -/// [NotInParallel] -/// public class MyTests -/// { -/// private StaticStateScope _stateScope; -/// -/// [SetUp] -/// public void SetUp() -/// { -/// _stateScope = new StaticStateScope( -/// () => MyClass.StaticProperty, -/// value => MyClass.StaticProperty = value, -/// () => AnotherClass.StaticField, -/// value => AnotherClass.StaticField = value); -/// -/// // Now safe to modify static state -/// } -/// -/// [TearDown] -/// public void TearDown() -/// { -/// _stateScope?.Dispose(); -/// } -/// } -/// -/// -public sealed class StaticStateScope : IDisposable -{ - private readonly List _restoreActions = []; - private bool _disposed; - - /// - /// Initializes a new instance of the class with multiple state capture/restore pairs. - /// - /// Pairs of getter functions and setter actions. Each pair should be: getter function, setter action. - public StaticStateScope(params object[] stateCaptures) - { - ArgumentNullException.ThrowIfNull(stateCaptures); - - if (stateCaptures.Length % 2 != 0) - { - throw new ArgumentException("State captures must come in pairs of (getter, setter)", nameof(stateCaptures)); - } - - for (var i = 0; i < stateCaptures.Length; i += 2) - { - if (stateCaptures[i] is not Delegate getter) - { - throw new ArgumentException($"Element at index {i} must be a Func getter", nameof(stateCaptures)); - } - - if (stateCaptures[i + 1] is not Delegate setter) - { - throw new ArgumentException($"Element at index {i + 1} must be an Action setter", nameof(stateCaptures)); - } - - // Capture the current value by invoking the getter - var currentValue = getter.DynamicInvoke(); - - // Store a restore action that will set the value back - _restoreActions.Add(() => setter.DynamicInvoke(currentValue)); - } - } - - /// - /// Restores all captured static state to their original values. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - foreach (var restoreAction in _restoreActions) - { - restoreAction(); - } - - _disposed = true; - } -} diff --git a/src/tests/ReactiveUI.Tests/InteractionBinding/InteractionBinderImplementationTests.cs b/src/tests/ReactiveUI.Tests/InteractionBinding/InteractionBinderImplementationTests.cs index 7585959f89..ebd33fec87 100644 --- a/src/tests/ReactiveUI.Tests/InteractionBinding/InteractionBinderImplementationTests.cs +++ b/src/tests/ReactiveUI.Tests/InteractionBinding/InteractionBinderImplementationTests.cs @@ -3,66 +3,101 @@ // 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; +using ReactiveUI.Tests.Mocks; + +namespace ReactiveUI.Tests.InteractionBinding; public class InteractionBinderImplementationTests { /// - /// Tests that make sure that the we receive output from task handler. + /// Tests to confirm nested interaction should receive output from observable handler. /// /// A task to monitor the progress. [Test] - public async Task ReceiveOutputFromTaskHandler() + public async Task NestedInteractionShouldReceiveOutputFromObservableHandler() { - var vm = new InteractionBindViewModel(); - var view = new InteractionBindView { ViewModel = vm }; + var vm = new InteractionAncestorViewModel(); + var view = new InteractionAncestorView { ViewModel = vm }; var disposable = view.BindInteraction( vm, - vm => vm.Interaction1, + vm => vm.InteractionViewModel.Interaction1, input => - { - input.SetOutput(true); - return Task.CompletedTask; - }); + { + input.SetOutput(true); + return Observable.Return(Unit.Default); + }); - var isDeletionConfirmed = await vm.Interaction1.Handle("123"); + var isDeletionConfirmed = await vm.InteractionViewModel.Interaction1.Handle("123"); await Assert.That(isDeletionConfirmed).IsTrue(); } /// - /// Test that we receive output from the observable handler. + /// Tests to confirm nested interaction should receive output from task handler. /// /// A task to monitor the progress. [Test] - public async Task ReceiveOutputFromObservableHandler() + public async Task NestedInteractionShouldReceiveOutputFromTaskHandler() { - var vm = new InteractionBindViewModel(); - var view = new InteractionBindView { ViewModel = vm }; + var vm = new InteractionAncestorViewModel(); + var view = new InteractionAncestorView { ViewModel = vm }; var disposable = view.BindInteraction( vm, - vm => vm.Interaction1, + vm => vm.InteractionViewModel.Interaction1, input => { input.SetOutput(true); - return Observable.Return(Unit.Default); + return Task.CompletedTask; }); - var isDeletionConfirmed = await vm.Interaction1.Handle("123"); + var isDeletionConfirmed = await vm.InteractionViewModel.Interaction1.Handle("123"); await Assert.That(isDeletionConfirmed).IsTrue(); } /// - /// Test that checks that the receive output from task handler when view model was initially null. + /// Test that confirms nested view model should be garbage collected when overwritten. + /// + /// A representing the asynchronous operation. + [Test] + public async Task NestedViewModelShouldBeGarbageCollectedWhenOverwritten() + { + static (IDisposable, WeakReference) GetWeakReference() + { + var vm = new InteractionAncestorViewModel { InteractionViewModel = new InteractionBindViewModel() }; + var view = new InteractionAncestorView { ViewModel = vm }; + var weakRef = new WeakReference(vm.InteractionViewModel); + var disposable = view.BindInteraction( + vm, + vm => vm.InteractionViewModel.Interaction1, + input => + { + input.SetOutput(true); + return Observable.Return(Unit.Default); + }); + vm.InteractionViewModel = new InteractionBindViewModel(); + + return (disposable, weakRef); + } + + var (disposable, weakRef) = GetWeakReference(); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + + await Assert.That(weakRef.IsAlive).IsFalse(); + } + + /// + /// Test that we receive output from the observable handler. /// /// A task to monitor the progress. [Test] - public async Task ReceiveOutputFromTaskHandlerWhenViewModelWasInitiallyNull() + public async Task ReceiveOutputFromObservableHandler() { - InteractionBindViewModel? vm = null; + var vm = new InteractionBindViewModel(); var view = new InteractionBindView { ViewModel = vm }; var disposable = view.BindInteraction( @@ -71,18 +106,16 @@ public async Task ReceiveOutputFromTaskHandlerWhenViewModelWasInitiallyNull() input => { input.SetOutput(true); - return Task.CompletedTask; + return Observable.Return(Unit.Default); }); - view.ViewModel = new InteractionBindViewModel(); - - var isDeletionConfirmed = await view.ViewModel.Interaction1.Handle("123"); + var isDeletionConfirmed = await vm.Interaction1.Handle("123"); await Assert.That(isDeletionConfirmed).IsTrue(); } /// - /// Test that checks that the receive output from observable handler when view model was initially null. + /// Test that checks that the receive output from observable handler when view model was initially null. /// /// A task to monitor the progress. [Test] @@ -108,11 +141,11 @@ public async Task ReceiveOutputFromObservableHandlerWhenViewModelWasInitiallyNul } /// - /// Tests to make sure that it unregisters the task handler when view model is set to null. + /// Tests that make sure that the we receive output from task handler. /// - /// A representing the asynchronous operation. + /// A task to monitor the progress. [Test] - public async Task UnregisterTaskHandlerWhenViewModelIsSetToNull() + public async Task ReceiveOutputFromTaskHandler() { var vm = new InteractionBindViewModel(); var view = new InteractionBindView { ViewModel = vm }; @@ -126,19 +159,19 @@ public async Task UnregisterTaskHandlerWhenViewModelIsSetToNull() return Task.CompletedTask; }); - view.ViewModel = null; + var isDeletionConfirmed = await vm.Interaction1.Handle("123"); - await Assert.That(async () => await vm.Interaction1.Handle("123").ToTask()).Throws>(); + await Assert.That(isDeletionConfirmed).IsTrue(); } /// - /// Tests to make sure that it unregisters the observable handler when view model is set to null. + /// Test that checks that the receive output from task handler when view model was initially null. /// - /// A representing the asynchronous operation. + /// A task to monitor the progress. [Test] - public async Task UnregisterObservableHandlerWhenViewModelIsSetToNull() + public async Task ReceiveOutputFromTaskHandlerWhenViewModelWasInitiallyNull() { - var vm = new InteractionBindViewModel(); + InteractionBindViewModel? vm = null; var view = new InteractionBindView { ViewModel = vm }; var disposable = view.BindInteraction( @@ -147,42 +180,48 @@ public async Task UnregisterObservableHandlerWhenViewModelIsSetToNull() input => { input.SetOutput(true); - return Observable.Return(Unit.Default); + return Task.CompletedTask; }); - view.ViewModel = null; + view.ViewModel = new InteractionBindViewModel(); - await Assert.That(async () => await vm.Interaction1.Handle("123").ToTask()).Throws>(); + var isDeletionConfirmed = await view.ViewModel.Interaction1.Handle("123"); + + await Assert.That(isDeletionConfirmed).IsTrue(); } /// - /// Tests to make sure that it unregisters the task handler from overwritten view model. + /// Tests to make sure that it registers the observable handler to newly assigned nested view model. /// + /// A task to monitor the progress. [Test] - public void UnregisterTaskHandlerFromOverwrittenViewModel() + public async Task RegisterObservableHandlerToNewlyAssignedNestedViewModel() { - var vm = new InteractionBindViewModel(); - var view = new InteractionBindView { ViewModel = vm }; + var vm = new InteractionAncestorViewModel { InteractionViewModel = new InteractionBindViewModel() }; + var view = new InteractionAncestorView { ViewModel = vm }; var disposable = view.BindInteraction( vm, - vm => vm.Interaction1, + vm => vm.InteractionViewModel.Interaction1, input => { input.SetOutput(true); - return Task.CompletedTask; + return Observable.Return(Unit.Default); }); - view.ViewModel = new InteractionBindViewModel(); + vm.InteractionViewModel = new InteractionBindViewModel(); - _ = Assert.ThrowsAsync>(() => vm.Interaction1.Handle("123").ToTask()); + var isDeletionConfirmed = await vm.InteractionViewModel.Interaction1.Handle("123"); + + await Assert.That(isDeletionConfirmed).IsTrue(); } /// - /// Tests to make sure that it unregisters the observable handler from overwritten view model. + /// Tests to make sure that it registers the observable handler to newly assigned view model. /// + /// A task to monitor the progress. [Test] - public void UnregisterObservableHandlerFromOverwrittenViewModel() + public async Task RegisterObservableHandlerToNewlyAssignedViewModel() { var vm = new InteractionBindViewModel(); var view = new InteractionBindView { ViewModel = vm }; @@ -198,41 +237,43 @@ public void UnregisterObservableHandlerFromOverwrittenViewModel() view.ViewModel = new InteractionBindViewModel(); - _ = Assert.ThrowsAsync>(() => vm.Interaction1.Handle("123").ToTask()); + var isDeletionConfirmed = await view.ViewModel.Interaction1.Handle("123"); + + await Assert.That(isDeletionConfirmed).IsTrue(); } /// - /// Tests to make sure that it registers the task handler to newly assigned view model. + /// Tests to make sure that it registers the task handler to newly assigned nested view model. /// /// A task to monitor the progress. [Test] - public async Task RegisterTaskHandlerToNewlyAssignedViewModel() + public async Task RegisterTaskHandlerToNewlyAssignedNestedViewModel() { - var vm = new InteractionBindViewModel(); - var view = new InteractionBindView { ViewModel = vm }; + var vm = new InteractionAncestorViewModel { InteractionViewModel = new InteractionBindViewModel() }; + var view = new InteractionAncestorView { ViewModel = vm }; var disposable = view.BindInteraction( vm, - vm => vm.Interaction1, + vm => vm.InteractionViewModel.Interaction1, input => { input.SetOutput(true); - return Task.CompletedTask; + return Observable.Return(Unit.Default); }); - view.ViewModel = new InteractionBindViewModel(); + vm.InteractionViewModel = new InteractionBindViewModel(); - var isDeletionConfirmed = await view.ViewModel.Interaction1.Handle("123"); + var isDeletionConfirmed = await vm.InteractionViewModel.Interaction1.Handle("123"); await Assert.That(isDeletionConfirmed).IsTrue(); } /// - /// Tests to make sure that it registers the observable handler to newly assigned view model. + /// Tests to make sure that it registers the task handler to newly assigned view model. /// /// A task to monitor the progress. [Test] - public async Task RegisterObservableHandlerToNewlyAssignedViewModel() + public async Task RegisterTaskHandlerToNewlyAssignedViewModel() { var vm = new InteractionBindViewModel(); var view = new InteractionBindView { ViewModel = vm }; @@ -243,7 +284,7 @@ public async Task RegisterObservableHandlerToNewlyAssignedViewModel() input => { input.SetOutput(true); - return Observable.Return(Unit.Default); + return Task.CompletedTask; }); view.ViewModel = new InteractionBindViewModel(); @@ -254,12 +295,12 @@ public async Task RegisterObservableHandlerToNewlyAssignedViewModel() } /// - /// Tests to confirm nested interaction should receive output from task handler. + /// Tests to make sure that it unregisters the observable handler from overwritten nested view model. /// - /// A task to monitor the progress. [Test] - public async Task NestedInteractionShouldReceiveOutputFromTaskHandler() + public void UnregisterObservableHandlerFromOverwrittenNestedViewModel() { + var firstInteractionVm = new InteractionBindViewModel(); var vm = new InteractionAncestorViewModel(); var view = new InteractionAncestorView { ViewModel = vm }; @@ -269,97 +310,96 @@ public async Task NestedInteractionShouldReceiveOutputFromTaskHandler() input => { input.SetOutput(true); - return Task.CompletedTask; + return Observable.Return(Unit.Default); }); - var isDeletionConfirmed = await vm.InteractionViewModel.Interaction1.Handle("123"); + view.ViewModel.InteractionViewModel = new InteractionBindViewModel(); - await Assert.That(isDeletionConfirmed).IsTrue(); + _ = Assert.ThrowsAsync>(() => + firstInteractionVm.Interaction1.Handle("123").ToTask()); } /// - /// Tests to confirm nested interaction should receive output from observable handler. + /// Tests to make sure that it unregisters the observable handler from overwritten view model. /// - /// A task to monitor the progress. [Test] - public async Task NestedInteractionShouldReceiveOutputFromObservableHandler() + public void UnregisterObservableHandlerFromOverwrittenViewModel() { - var vm = new InteractionAncestorViewModel(); - var view = new InteractionAncestorView { ViewModel = vm }; + var vm = new InteractionBindViewModel(); + var view = new InteractionBindView { ViewModel = vm }; var disposable = view.BindInteraction( vm, - vm => vm.InteractionViewModel.Interaction1, + vm => vm.Interaction1, input => { input.SetOutput(true); return Observable.Return(Unit.Default); }); - var isDeletionConfirmed = await vm.InteractionViewModel.Interaction1.Handle("123"); + view.ViewModel = new InteractionBindViewModel(); - await Assert.That(isDeletionConfirmed).IsTrue(); + _ = Assert.ThrowsAsync>(() => + vm.Interaction1.Handle("123").ToTask()); } /// - /// Test to confirm that unregistering the task handler from overwritten nested view model. + /// Tests to make sure that it unregisters the observable handler when binding is disposed. /// [Test] - public void UnregisterTaskHandlerFromOverwrittenNestedViewModel() + public void UnregisterObservableHandlerWhenBindingIsDisposed() { - var firstInteractionVm = new InteractionBindViewModel(); - var vm = new InteractionAncestorViewModel(); - var view = new InteractionAncestorView { ViewModel = vm }; + var vm = new InteractionBindViewModel(); + var view = new InteractionBindView { ViewModel = vm }; var disposable = view.BindInteraction( vm, - vm => vm.InteractionViewModel.Interaction1, + vm => vm.Interaction1, input => { input.SetOutput(true); - return Task.CompletedTask; + return Observable.Return(Unit.Default); }); - view.ViewModel.InteractionViewModel = new InteractionBindViewModel(); + disposable.Dispose(); - _ = Assert.ThrowsAsync>(() => firstInteractionVm.Interaction1.Handle("123").ToTask()); + _ = Assert.ThrowsAsync>(() => + vm.Interaction1.Handle("123").ToTask()); } /// - /// Tests to make sure that it unregisters the observable handler from overwritten nested view model. + /// Tests to make sure that it unregisters the observable handler when view model is set to null. /// + /// A representing the asynchronous operation. [Test] - public void UnregisterObservableHandlerFromOverwrittenNestedViewModel() + public async Task UnregisterObservableHandlerWhenViewModelIsSetToNull() { - var firstInteractionVm = new InteractionBindViewModel(); - var vm = new InteractionAncestorViewModel(); - var view = new InteractionAncestorView { ViewModel = vm }; + var vm = new InteractionBindViewModel(); + var view = new InteractionBindView { ViewModel = vm }; var disposable = view.BindInteraction( vm, - vm => vm.InteractionViewModel.Interaction1, + vm => vm.Interaction1, input => { input.SetOutput(true); return Observable.Return(Unit.Default); }); - view.ViewModel.InteractionViewModel = new InteractionBindViewModel(); + view.ViewModel = null; - _ = Assert.ThrowsAsync>(() => firstInteractionVm.Interaction1.Handle("123").ToTask()); + await Assert.That(async () => await vm.Interaction1.Handle("123").ToTask()) + .Throws>(); } /// - /// Tests to make sure that it registers the task handler to newly assigned nested view model. + /// Test to confirm that unregistering the task handler from overwritten nested view model. /// - /// A task to monitor the progress. [Test] - public async Task RegisterTaskHandlerToNewlyAssignedNestedViewModel() + public void UnregisterTaskHandlerFromOverwrittenNestedViewModel() { - var vm = new InteractionAncestorViewModel() - { - InteractionViewModel = new InteractionBindViewModel() - }; + var firstInteractionVm = new InteractionBindViewModel(); + var vm = new InteractionAncestorViewModel(); var view = new InteractionAncestorView { ViewModel = vm }; var disposable = view.BindInteraction( @@ -368,47 +408,41 @@ public async Task RegisterTaskHandlerToNewlyAssignedNestedViewModel() input => { input.SetOutput(true); - return Observable.Return(Unit.Default); + return Task.CompletedTask; }); - vm.InteractionViewModel = new InteractionBindViewModel(); - - var isDeletionConfirmed = await vm.InteractionViewModel.Interaction1.Handle("123"); + view.ViewModel.InteractionViewModel = new InteractionBindViewModel(); - await Assert.That(isDeletionConfirmed).IsTrue(); + _ = Assert.ThrowsAsync>(() => + firstInteractionVm.Interaction1.Handle("123").ToTask()); } /// - /// Tests to make sure that it registers the observable handler to newly assigned nested view model. + /// Tests to make sure that it unregisters the task handler from overwritten view model. /// - /// A task to monitor the progress. [Test] - public async Task RegisterObservableHandlerToNewlyAssignedNestedViewModel() + public void UnregisterTaskHandlerFromOverwrittenViewModel() { - var vm = new InteractionAncestorViewModel() - { - InteractionViewModel = new InteractionBindViewModel() - }; - var view = new InteractionAncestorView { ViewModel = vm }; + var vm = new InteractionBindViewModel(); + var view = new InteractionBindView { ViewModel = vm }; var disposable = view.BindInteraction( vm, - vm => vm.InteractionViewModel.Interaction1, + vm => vm.Interaction1, input => { input.SetOutput(true); - return Observable.Return(Unit.Default); + return Task.CompletedTask; }); - vm.InteractionViewModel = new InteractionBindViewModel(); - - var isDeletionConfirmed = await vm.InteractionViewModel.Interaction1.Handle("123"); + view.ViewModel = new InteractionBindViewModel(); - await Assert.That(isDeletionConfirmed).IsTrue(); + _ = Assert.ThrowsAsync>(() => + vm.Interaction1.Handle("123").ToTask()); } /// - /// Tests to make sure that it unregisters the task handler when binding is disposed. + /// Tests to make sure that it unregisters the task handler when binding is disposed. /// [Test] public void UnregisterTaskHandlerWhenBindingIsDisposed() @@ -427,14 +461,16 @@ public void UnregisterTaskHandlerWhenBindingIsDisposed() disposable.Dispose(); - _ = Assert.ThrowsAsync>(() => vm.Interaction1.Handle("123").ToTask()); + _ = Assert.ThrowsAsync>(() => + vm.Interaction1.Handle("123").ToTask()); } /// - /// Tests to make sure that it unregisters the observable handler when binding is disposed. + /// Tests to make sure that it unregisters the task handler when view model is set to null. /// + /// A representing the asynchronous operation. [Test] - public void UnregisterObservableHandlerWhenBindingIsDisposed() + public async Task UnregisterTaskHandlerWhenViewModelIsSetToNull() { var vm = new InteractionBindViewModel(); var view = new InteractionBindView { ViewModel = vm }; @@ -445,18 +481,19 @@ public void UnregisterObservableHandlerWhenBindingIsDisposed() input => { input.SetOutput(true); - return Observable.Return(Unit.Default); + return Task.CompletedTask; }); - disposable.Dispose(); + view.ViewModel = null; - _ = Assert.ThrowsAsync>(() => vm.Interaction1.Handle("123").ToTask()); + await Assert.That(async () => await vm.Interaction1.Handle("123").ToTask()) + .Throws>(); } /// - /// Test that confirms the view model should be garbage collected when overwritten. + /// Test that confirms the view model should be garbage collected when overwritten. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ViewModelShouldBeGarbageCollectedWhenOverwritten() { @@ -468,45 +505,12 @@ public async Task ViewModelShouldBeGarbageCollectedWhenOverwritten() var disposable = view.BindInteraction( vm, vm => vm.Interaction1, - input => - { - input.SetOutput(true); - return Task.CompletedTask; - }); - view.ViewModel = new InteractionBindViewModel(); - - return (disposable, weakRef); - } - - var (disposable, weakRef) = GetWeakReference(); - - GC.Collect(); - GC.WaitForPendingFinalizers(); - - await Assert.That(weakRef.IsAlive).IsFalse(); - } - - /// - /// Test that confirms nested view model should be garbage collected when overwritten. - /// - /// A representing the asynchronous operation. - [Test] - public async Task NestedViewModelShouldBeGarbageCollectedWhenOverwritten() - { - static (IDisposable, WeakReference) GetWeakReference() - { - var vm = new InteractionAncestorViewModel() { InteractionViewModel = new InteractionBindViewModel() }; - var view = new InteractionAncestorView { ViewModel = vm }; - var weakRef = new WeakReference(vm.InteractionViewModel); - var disposable = view.BindInteraction( - vm, - vm => vm.InteractionViewModel.Interaction1, input => { input.SetOutput(true); - return Observable.Return(Unit.Default); + return Task.CompletedTask; }); - vm.InteractionViewModel = new InteractionBindViewModel(); + view.ViewModel = new InteractionBindViewModel(); return (disposable, weakRef); } diff --git a/src/tests/ReactiveUI.Tests/InteractionsTest.cs b/src/tests/ReactiveUI.Tests/InteractionsTest.cs index e1e2177e28..726f8117b5 100644 --- a/src/tests/ReactiveUI.Tests/InteractionsTest.cs +++ b/src/tests/ReactiveUI.Tests/InteractionsTest.cs @@ -5,59 +5,32 @@ using DynamicData; -using Microsoft.Reactive.Testing; - -using ReactiveUI.Testing; - namespace ReactiveUI.Tests; /// -/// Tests interactions. +/// Tests interactions. /// public class InteractionsTest { /// - /// Tests that registers null handler should cause exception. + /// Test that attempting to get interaction output before it has been set should cause exception. /// + /// A representing the asynchronous operation. [Test] - public void RegisterNullHandlerShouldCauseException() + public async Task AttemptingToGetInteractionOutputBeforeItHasBeenSetShouldCauseException() { var interaction = new Interaction(); - Assert.Throws(() => interaction.RegisterHandler((Action>)null!)); - Assert.Throws(() => interaction.RegisterHandler(null!)); - Assert.Throws(() => interaction.RegisterHandler((Func, IObservable>)null!)); - } - - /// - /// Tests that unhandled interactions should cause exception. - /// - /// A representing the asynchronous operation. - [Test] - public async Task UnhandledInteractionsShouldCauseException() - { - var interaction = new Interaction(); - var ex = Assert.Throws>(() => interaction.Handle("foo").FirstAsync().Wait()); - using (Assert.Multiple()) - { - await Assert.That(ex.Interaction).IsSameReferenceAs(interaction); - await Assert.That(ex.Input).IsEqualTo("foo"); - } + interaction.RegisterHandler(context => { _ = ((InteractionContext)context).GetOutput(); }); - interaction.RegisterHandler(_ => { }); - interaction.RegisterHandler(_ => { }); - ex = Assert.Throws>(() => interaction.Handle("bar").FirstAsync().Wait()); - using (Assert.Multiple()) - { - await Assert.That(ex.Interaction).IsSameReferenceAs(interaction); - await Assert.That(ex.Input).IsEqualTo("bar"); - } + var ex = Assert.Throws(() => interaction.Handle(Unit.Default).Subscribe()); + await Assert.That(ex.Message).IsEqualTo("Output has not been set."); } /// - /// Test that attempting to set interaction output more than once should cause exception. + /// Test that attempting to set interaction output more than once should cause exception. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task AttemptingToSetInteractionOutputMoreThanOnceShouldCauseException() { @@ -74,105 +47,124 @@ public async Task AttemptingToSetInteractionOutputMoreThanOnceShouldCauseExcepti } /// - /// Test that attempting to get interaction output before it has been set should cause exception. + /// Tests that Handled interactions should not cause exception. /// - /// A representing the asynchronous operation. [Test] - public async Task AttemptingToGetInteractionOutputBeforeItHasBeenSetShouldCauseException() + public void HandledInteractionsShouldNotCauseException() { - var interaction = new Interaction(); - - interaction.RegisterHandler(context => - { - var output = ((InteractionContext)context).GetOutput(); - }); + var interaction = new Interaction(); + interaction.RegisterHandler(static c => c.SetOutput(true)); - var ex = Assert.Throws(() => interaction.Handle(Unit.Default).Subscribe()); - await Assert.That(ex.Message).IsEqualTo("Output has not been set."); + interaction.Handle(Unit.Default).FirstAsync().Wait(); } /// - /// Tests that Handled interactions should not cause exception. + /// Tests that Handlers are executed on handler scheduler. /// + /// A representing the asynchronous operation. [Test] - public void HandledInteractionsShouldNotCauseException() + [TestExecutor] + public async Task HandlersAreExecutedOnHandlerScheduler() { - var interaction = new Interaction(); - interaction.RegisterHandler(static c => c.SetOutput(true)); + var scheduler = TestContext.Current!.GetScheduler(); + var interaction = new Interaction(scheduler); - interaction.Handle(Unit.Default).FirstAsync().Wait(); + using (interaction.RegisterHandler(x => x.SetOutput("done"))) + { + var handled = false; + interaction + .Handle(Unit.Default) + .Subscribe(_ => handled = true); + + // With ImmediateScheduler, handlers execute immediately + await Assert.That(handled).IsTrue(); + } } /// - /// Tests that Handlers are executed on handler scheduler. + /// Test that handlers can contain asynchronous code. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task HandlersAreExecutedOnHandlerScheduler() => - await new TestScheduler().With(async scheduler => + + [TestExecutor] + public async Task HandlersCanContainAsynchronousCode() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + var interaction = new Interaction(); + + // even though handler B is "slow" (i.e. mimicks waiting for the user), it takes precedence over A, so we expect A to never even be called + var handler1AWasCalled = false; + var handler1A = interaction.RegisterHandler(x => { - var interaction = new Interaction(scheduler); + x.SetOutput("A"); + handler1AWasCalled = true; + }); + var handler1B = interaction.RegisterHandler(x => + Observables + .Unit + .Delay(TimeSpan.FromSeconds(1), scheduler) + .Do(_ => x.SetOutput("B"))); - using (interaction.RegisterHandler(x => x.SetOutput("done"))) - { - var handled = false; - interaction - .Handle(Unit.Default) - .Subscribe(_ => handled = true); + using (handler1A) + using (handler1B) + { + interaction + .Handle(Unit.Default) + .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var result).Subscribe(); - await Assert.That(handled).IsFalse(); + 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("B"); + } - scheduler.Start(); - await Assert.That(handled).IsTrue(); - } - }); + await Assert.That(handler1AWasCalled).IsFalse(); + } /// - /// Test that Nested handlers are executed in reverse order of subscription. + /// Test that handlers can contain asynchronous code via tasks. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task NestedHandlersAreExecutedInReverseOrderOfSubscription() + public async Task HandlersCanContainAsynchronousCodeViaTasks() { var interaction = new Interaction(); - using (interaction.RegisterHandler(static x => x.SetOutput("A"))) + interaction.RegisterHandler(context => { - await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("A"); - using (interaction.RegisterHandler(static x => x.SetOutput("B"))) - { - await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("B"); - using (interaction.RegisterHandler(static x => x.SetOutput("C"))) - { - await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("C"); - } + context.SetOutput("result"); + return Task.FromResult(true); + }); - await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("B"); - } + string? result = null; + interaction + .Handle(Unit.Default) + .Subscribe(r => result = r); - await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("A"); - } + await Assert.That(result).IsEqualTo("result"); } /// - /// Tests that handlers can opt not to handle the interaction. + /// Tests that handlers can opt not to handle the interaction. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task HandlersCanOptNotToHandleTheInteraction() { var interaction = new Interaction(); var handler1A = interaction.RegisterHandler(static x => x.SetOutput("A")); - var handler1B = interaction.RegisterHandler( - static x => + var handler1B = interaction.RegisterHandler(static x => + { + // only handle if the input is true + if (x.Input) { - // only handle if the input is true - if (x.Input) - { - x.SetOutput("B"); - } - }); + x.SetOutput("B"); + } + }); var handler1C = interaction.RegisterHandler(static x => x.SetOutput("C")); using (handler1A) @@ -202,84 +194,89 @@ public async Task HandlersCanOptNotToHandleTheInteraction() } /// - /// Test that handlers can contain asynchronous code. + /// Tests that handlers returning observables can return any kind of observable. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task HandlersCanContainAsynchronousCode() + public async Task HandlersReturningObservablesCanReturnAnyKindOfObservable() { - var scheduler = new TestScheduler(); var interaction = new Interaction(); - // even though handler B is "slow" (i.e. mimicks waiting for the user), it takes precedence over A, so we expect A to never even be called - var handler1AWasCalled = false; - var handler1A = interaction.RegisterHandler( - x => - { - x.SetOutput("A"); - handler1AWasCalled = true; - }); - var handler1B = interaction.RegisterHandler( - x => - Observables - .Unit - .Delay(TimeSpan.FromSeconds(1), scheduler) - .Do(_ => x.SetOutput("B"))); - - using (handler1A) - using (handler1B) - { - interaction - .Handle(Unit.Default) - .ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var result).Subscribe(); - - await Assert.That(result).IsEmpty(); - scheduler.AdvanceBy(TimeSpan.FromSeconds(0.5).Ticks); - await Assert.That(result).IsEmpty(); - scheduler.AdvanceBy(TimeSpan.FromSeconds(0.6).Ticks); - await Assert.That(result).Count().IsEqualTo(1); - await Assert.That(result[0]).IsEqualTo("B"); - } + _ = interaction.RegisterHandler(x => + Observable + .Return(42) + .Do(_ => x.SetOutput("result"))); - await Assert.That(handler1AWasCalled).IsFalse(); + var result = interaction.Handle(Unit.Default).FirstAsync().Wait(); + await Assert.That(result).IsEqualTo("result"); } /// - /// Test that handlers can contain asynchronous code via tasks. + /// Test that Nested handlers are executed in reverse order of subscription. /// + /// A representing the asynchronous operation. [Test] - public void HandlersCanContainAsynchronousCodeViaTasks() + public async Task NestedHandlersAreExecutedInReverseOrderOfSubscription() { var interaction = new Interaction(); - interaction.RegisterHandler(context => + using (interaction.RegisterHandler(static x => x.SetOutput("A"))) { - context.SetOutput("result"); - return Task.FromResult(true); - }); + await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("A"); + using (interaction.RegisterHandler(static x => x.SetOutput("B"))) + { + await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("B"); + using (interaction.RegisterHandler(static x => x.SetOutput("C"))) + { + await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("C"); + } - string? result = null; - interaction - .Handle(Unit.Default) - .Subscribe(r => result = r); + await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("B"); + } + + await Assert.That(interaction.Handle(Unit.Default).FirstAsync().Wait()).IsEqualTo("A"); + } } /// - /// Tests that handlers returning observables can return any kind of observable. + /// Tests that registers null handler should cause exception. /// - /// A representing the asynchronous operation. [Test] - public async Task HandlersReturningObservablesCanReturnAnyKindOfObservable() + public void RegisterNullHandlerShouldCauseException() { - var interaction = new Interaction(); + var interaction = new Interaction(); + + Assert.Throws(() => + interaction.RegisterHandler((Action>)null!)); + Assert.Throws(() => interaction.RegisterHandler(null!)); + Assert.Throws(() => + interaction.RegisterHandler((Func, IObservable>)null!)); + } - var handler = interaction.RegisterHandler( - x => - Observable - .Return(42) - .Do(_ => x.SetOutput("result"))); + /// + /// Tests that unhandled interactions should cause exception. + /// + /// A representing the asynchronous operation. + [Test] + public async Task UnhandledInteractionsShouldCauseException() + { + var interaction = new Interaction(); + var ex = Assert.Throws>(() => + interaction.Handle("foo").FirstAsync().Wait()); + using (Assert.Multiple()) + { + await Assert.That(ex.Interaction).IsSameReferenceAs(interaction); + await Assert.That(ex.Input).IsEqualTo("foo"); + } - var result = interaction.Handle(Unit.Default).FirstAsync().Wait(); - await Assert.That(result).IsEqualTo("result"); + interaction.RegisterHandler(_ => { }); + interaction.RegisterHandler(_ => { }); + ex = Assert.Throws>(() => + interaction.Handle("bar").FirstAsync().Wait()); + using (Assert.Multiple()) + { + await Assert.That(ex.Interaction).IsSameReferenceAs(interaction); + await Assert.That(ex.Input).IsEqualTo("bar"); + } } } diff --git a/src/tests/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs b/src/tests/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs new file mode 100644 index 0000000000..c393d82293 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs @@ -0,0 +1,577 @@ +// Copyright (c) 2025 .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.AppBuilder; + +namespace ReactiveUI.Tests.Locator; + +/// +/// Comprehensive test suite for . +/// Tests cover mapping, unmapping, resolution with contracts, AOT compatibility, and thread safety. +/// Uses to ensure proper AppLocator isolation between tests. +/// +[NotInParallel] +[TestExecutor] +public class DefaultViewLocatorTests +{ + /// + /// Verifies that returns the locator instance + /// to support fluent API chaining. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Map_AllowsChaining() + { + var locator = new DefaultViewLocator(); + + var result = locator + .Map(() => new TestView()) + .Map(() => new TestView2()); + + await Assert.That(result).IsNotNull(); + await Assert.That(result).IsTypeOf(); + } + + /// + /// Verifies that throws + /// when factory parameter is null. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Map_NullFactory_ThrowsArgumentNullException() + { + var locator = new DefaultViewLocator(); + + await Assert.ThrowsAsync(async () => + { + locator.Map(null!); + await Task.CompletedTask; + }); + } + + /// + /// Verifies that calling multiple times + /// for the same view model and contract overwrites the previous mapping. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Map_OverwritesExistingMapping() + { + var locator = new DefaultViewLocator(); + var callCount = 0; + + locator + .Map(() => + { + callCount++; + return new TestView(); + }) + .Map(() => + { + callCount += 10; + return new TestView(); + }); + + locator.ResolveView(); + + await Assert.That(callCount).IsEqualTo(10); + } + + /// + /// Verifies that registers a view factory + /// that can be resolved via . + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Map_RegistersViewFactory() + { + var locator = new DefaultViewLocator(); + + locator.Map(() => new TestView()); + + var view = locator.ResolveView(); + + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsTypeOf(); + } + + /// + /// Verifies that is thread-safe + /// and does not throw when called concurrently from multiple threads. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Map_ThreadSafe_ConcurrentMapsDontThrow() + { + var locator = new DefaultViewLocator(); + var tasks = new List(); + + for (var i = 0; i < 100; i++) + { + var contract = $"contract{i}"; + tasks.Add(Task.Run(() => { locator.Map(() => new TestView(), contract); })); + } + + await Task.WhenAll(tasks); + + for (var i = 0; i < 100; i++) + { + var view = locator.ResolveView($"contract{i}"); + await Assert.That(view).IsNotNull(); + } + } + + /// + /// Verifies that with contract parameter + /// registers contract-specific views that can be resolved separately. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Map_WithContract_RegistersContractSpecificView() + { + var locator = new DefaultViewLocator(); + + locator.Map(() => new TestView(), "mobile") + .Map(() => new TestViewAlt(), "desktop"); + + var mobileView = locator.ResolveView("mobile"); + var desktopView = locator.ResolveView("desktop"); + + await Assert.That(mobileView).IsTypeOf(); + await Assert.That(desktopView).IsTypeOf(); + } + + /// + /// Verifies that creates a new view instance + /// on each call using the registered factory. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Generic_CreatesNewInstanceOnEachCall() + { + var locator = new DefaultViewLocator(); + + locator.Map(() => new TestView()); + + var view1 = locator.ResolveView(); + var view2 = locator.ResolveView(); + + await Assert.That(view1).IsNotNull(); + await Assert.That(view2).IsNotNull(); + await Assert.That(ReferenceEquals(view1, view2)).IsFalse(); + } + + /// + /// Verifies that explicit mappings registered via + /// take priority over service locator registrations. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Generic_ExplicitMappingTakesPriorityOverServiceLocator() + { + var resolver = AppLocator.Current as IDependencyResolver; + ArgumentNullException.ThrowIfNull(resolver); + + resolver.Register(() => new TestViewAlt(), typeof(IViewFor)); + + try + { + var locator = new DefaultViewLocator(); + locator.Map(() => new TestView()); + + var view = locator.ResolveView(); + + await Assert.That(view).IsTypeOf(); + } + finally + { + // Clean up registration + resolver.UnregisterCurrent(typeof(IViewFor)); + } + } + + /// + /// Verifies that falls back + /// to querying the service locator when no explicit mapping exists. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Generic_FallsBackToServiceLocator() + { + var resolver = AppLocator.Current as IDependencyResolver; + ArgumentNullException.ThrowIfNull(resolver); + + resolver.Register(() => new TestView(), typeof(IViewFor)); + + try + { + var locator = new DefaultViewLocator(); + var view = locator.ResolveView(); + + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsTypeOf(); + } + finally + { + // Clean up registration + resolver.UnregisterCurrent(typeof(IViewFor)); + } + } + + /// + /// Verifies that returns null + /// when no mapping or service registration exists for the view model type. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Generic_ReturnsNullWhenNoMapping() + { + var locator = new DefaultViewLocator(); + + var view = locator.ResolveView(); + + await Assert.That(view).IsNull(); + } + + /// + /// Verifies that with contract + /// uses the explicit mapping registered for that contract. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Generic_WithContract_UsesExplicitMapping() + { + var locator = new DefaultViewLocator(); + + locator.Map(() => new TestView(), "mobile"); + + var view = locator.ResolveView("mobile"); + + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsTypeOf(); + } + + /// + /// Verifies that falls back + /// to the service locator and sets the view model property. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Instance_FallsBackToServiceLocator() + { + var resolver = AppLocator.Current as IDependencyResolver; + ArgumentNullException.ThrowIfNull(resolver); + + resolver.Register(() => new TestView(), typeof(IViewFor)); + + try + { + var locator = new DefaultViewLocator(); + var vm = new TestViewModel(); + var view = locator.ResolveView(vm); + + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsTypeOf(); + await Assert.That(view!.ViewModel).IsEqualTo(vm); + } + finally + { + // Clean up registration + resolver.UnregisterCurrent(typeof(IViewFor)); + } + } + + /// + /// Verifies that returns null + /// when the instance parameter is null. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Instance_ReturnsNullForNullInstance() + { + var locator = new DefaultViewLocator(); + + var view = locator.ResolveView(null); + + await Assert.That(view).IsNull(); + } + + /// + /// Verifies that returns null + /// when no mapping or service registration exists for the view model type. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Instance_ReturnsNullWhenNoMappingOrService() + { + var locator = new DefaultViewLocator(); + var vm = new TestViewModel(); + + var view = locator.ResolveView(vm); + + await Assert.That(view).IsNull(); + } + + /// + /// Verifies that sets + /// the property on the resolved view instance. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Instance_SetsViewModelProperty() + { + var locator = new DefaultViewLocator(); + var vm = new TestViewModel(); + + locator.Map(() => new TestView()); + + var view = locator.ResolveView(vm); + + await Assert.That(view).IsNotNull(); + await Assert.That(view!.ViewModel).IsEqualTo(vm); + } + + /// + /// Verifies that uses + /// explicit mappings registered via . + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Instance_UsesExplicitMapping() + { + var locator = new DefaultViewLocator(); + var vm = new TestViewModel(); + + locator.Map(() => new TestView()); + + var view = locator.ResolveView(vm); + + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsTypeOf(); + } + + /// + /// Verifies that with contract + /// uses the contract-specific mapping and sets the view model property. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_Instance_WithContract_UsesContractMapping() + { + var locator = new DefaultViewLocator(); + var vm = new TestViewModel(); + + locator.Map(() => new TestView(), "mobile"); + + var view = locator.ResolveView(vm, "mobile"); + + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsTypeOf(); + await Assert.That(view!.ViewModel).IsEqualTo(vm); + } + + /// + /// Verifies that is thread-safe + /// and does not throw when called concurrently from multiple threads. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ResolveView_ThreadSafe_ConcurrentResolvesDontThrow() + { + var locator = new DefaultViewLocator(); + locator.Map(() => new TestView()); + + var tasks = new List(); + for (var i = 0; i < 100; i++) + { + tasks.Add( + Task.Run(async () => + { + var view = locator.ResolveView(); + await Assert.That(view).IsNotNull(); + })); + } + + await Task.WhenAll(tasks); + } + + /// + /// Verifies that returns the locator instance + /// to support fluent API chaining. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Unmap_AllowsChaining() + { + var locator = new DefaultViewLocator(); + + locator.Map(() => new TestView(), "c1") + .Map(() => new TestView(), "c2"); + + var result = locator.Unmap("c1") + .Unmap("c2"); + + await Assert.That(result).IsNotNull(); + await Assert.That(result).IsTypeOf(); + } + + /// + /// Verifies that does not throw when + /// called for a contract that was never registered. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Unmap_NonExistentMapping_DoesNotThrow() + { + var locator = new DefaultViewLocator(); + + locator.Unmap("nonexistent"); + + await Assert.That(locator.ResolveView("nonexistent")).IsNull(); + } + + /// + /// Verifies that removes the default mapping + /// when called without a contract parameter. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Unmap_RemovesDefaultMapping() + { + var locator = new DefaultViewLocator(); + + locator.Map(() => new TestView()); + + await Assert.That(locator.ResolveView()).IsNotNull(); + + locator.Unmap(); + + await Assert.That(locator.ResolveView()).IsNull(); + } + + /// + /// Verifies that removes a previously + /// registered mapping for a specific contract. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Unmap_RemovesMappingForContract() + { + var locator = new DefaultViewLocator(); + + locator.Map(() => new TestView(), "mobile"); + + await Assert.That(locator.ResolveView("mobile")).IsNotNull(); + + locator.Unmap("mobile"); + + await Assert.That(locator.ResolveView("mobile")).IsNull(); + } + + /// + /// Verifies that is thread-safe + /// and does not throw when called concurrently from multiple threads. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Unmap_ThreadSafe_ConcurrentUnmapsDontThrow() + { + var locator = new DefaultViewLocator(); + + for (var i = 0; i < 100; i++) + { + locator.Map(() => new TestView(), $"contract{i}"); + } + + var tasks = new List(); + for (var i = 0; i < 100; i++) + { + var contract = $"contract{i}"; + tasks.Add(Task.Run(() => { locator.Unmap(contract); })); + } + + await Task.WhenAll(tasks); + + for (var i = 0; i < 100; i++) + { + var view = locator.ResolveView($"contract{i}"); + await Assert.That(view).IsNull(); + } + } + + /// + /// Test view implementing for . + /// + private sealed class TestView : IViewFor + { + /// + /// Gets or sets the strongly-typed view model. + /// + public TestViewModel? ViewModel { get; set; } + + /// + /// Gets or sets the view model. Implements . + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TestViewModel?)value; + } + } + + /// + /// Test view implementing for . + /// + private sealed class TestView2 : IViewFor + { + /// + /// Gets or sets the strongly-typed view model. + /// + public TestViewModel2? ViewModel { get; set; } + + /// + /// Gets or sets the view model. Implements . + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TestViewModel2?)value; + } + } + + /// + /// Alternative test view for , used to test contract-specific mappings. + /// + private sealed class TestViewAlt : IViewFor + { + /// + /// Gets or sets the strongly-typed view model. + /// + public TestViewModel? ViewModel { get; set; } + + /// + /// Gets or sets the view model. Implements . + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TestViewModel?)value; + } + } + + /// + /// Test view model used for testing view locator functionality. + /// + private sealed class TestViewModel : ReactiveObject + { + } + + /// + /// Second test view model used for testing multi-mapping scenarios. + /// + private sealed class TestViewModel2 : ReactiveObject + { + } +} diff --git a/src/tests/ReactiveUI.Tests/Locator/ViewLocatorTests.cs b/src/tests/ReactiveUI.Tests/Locator/ViewLocatorTests.cs new file mode 100644 index 0000000000..13f4b60dc7 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Locator/ViewLocatorTests.cs @@ -0,0 +1,144 @@ +// Copyright (c) 2025 .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; +using ReactiveUI.Tests.Utilities.AppBuilder; +using Splat.Builder; + +namespace ReactiveUI.Tests.Locator; + +/// +/// Comprehensive test suite for static class. +/// Tests cover the static property and initialization behavior. +/// Uses to ensure proper AppLocator isolation between tests. +/// +[NotInParallel] +[TestExecutor] +public class ViewLocatorTests +{ + /// + /// Verifies that can be configured using + /// the builder's ConfigureViewLocator method to map and resolve views. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Current_CanBeConfigured_EndToEnd() + { + // Reset and configure the ViewLocator + RxAppBuilder.ResetForTesting(); + AppBuilder.ResetBuilderStateForTests(); + + var builder = RxAppBuilder.CreateReactiveUIBuilder(); + builder.ConfigureViewLocator(locator => { locator.Map(() => new TestView()); }); + builder.WithCoreServices().BuildApp(); + + var current = ViewLocator.Current; + var view = current.ResolveView(); + + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsTypeOf(); + } + + /// + /// Verifies that can resolve views + /// using the default view locator registration mechanism. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Current_CanResolveViews_ViaServiceLocator() + { + var resolver = AppLocator.Current as IDependencyResolver; + ArgumentNullException.ThrowIfNull(resolver); + + // Register a view in the service locator + resolver.Register(() => new TestView(), typeof(IViewFor)); + + var locator = ViewLocator.Current; + var view = locator.ResolveView(); + + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsTypeOf(); + } + + /// + /// Verifies that returns a default + /// when ReactiveUI is initialized with core services. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Current_ReturnsDefaultViewLocator() + { + // AppBuilderTestExecutor initializes ReactiveUI with core services + var current = ViewLocator.Current; + + await Assert.That(current).IsNotNull(); + await Assert.That(current).IsTypeOf(); + } + + /// + /// Verifies that returns a new instance + /// after ReactiveUI is reset and re-initialized. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Current_ReturnsNewInstance_AfterReinitialization() + { + var current1 = ViewLocator.Current; + + // Simulate re-initialization + RxAppBuilder.ResetForTesting(); + AppBuilder.ResetBuilderStateForTests(); + RxAppBuilder.CreateReactiveUIBuilder() + .WithCoreServices() + .BuildApp(); + + var current2 = ViewLocator.Current; + + await Assert.That(ReferenceEquals(current1, current2)).IsFalse(); + await Assert.That(current2).IsNotNull(); + await Assert.That(current2).IsTypeOf(); + } + + /// + /// Verifies that returns the same instance + /// when called multiple times (singleton behavior). + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Current_ReturnsSameInstance_WhenCalledMultipleTimes() + { + var current1 = ViewLocator.Current; + var current2 = ViewLocator.Current; + + await Assert.That(ReferenceEquals(current1, current2)).IsTrue(); + } + + /// + /// Test view implementing for . + /// + private sealed class TestView : IViewFor + { + /// + /// Gets or sets the strongly-typed view model. + /// + public TestViewModel? ViewModel { get; set; } + + /// + /// Gets or sets the view model. Implements . + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TestViewModel?)value; + } + } + + /// + /// Test view model used for testing view locator functionality. + /// + private sealed class TestViewModel : ReactiveObject + { + } +} diff --git a/src/tests/ReactiveUI.Tests/MessageBus/MessageBusTest.cs b/src/tests/ReactiveUI.Tests/MessageBus/MessageBusTest.cs new file mode 100644 index 0000000000..873809ee4b --- /dev/null +++ b/src/tests/ReactiveUI.Tests/MessageBus/MessageBusTest.cs @@ -0,0 +1,793 @@ +// Copyright (c) 2025 .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 DynamicData; +using ReactiveUI.Tests.Utilities.MessageBus; + +namespace ReactiveUI.Tests.MessageBus; + +/// +/// Comprehensive test suite for MessageBus. +/// Tests cover all public methods, contracts, scheduling, and edge cases. +/// +[NotInParallel] +[TestExecutor] +public class MessageBusTest +{ + /// + /// Tests that MessageBus.Current property can be get and set. + /// Verifies the static Current property accessor functionality. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Current_CanGetAndSet() + { + var customBus = new ReactiveUI.MessageBus(); + var original = ReactiveUI.MessageBus.Current; + + try + { + ReactiveUI.MessageBus.Current = customBus; + await Assert.That(ReactiveUI.MessageBus.Current).IsEqualTo(customBus); + } + finally + { + ReactiveUI.MessageBus.Current = original; + } + } + + /// + /// Tests that IsRegistered returns true after Listen is called. + /// Verifies that subscribing via Listen registers the type. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task IsRegistered_AfterListen_ReturnsTrue() + { + var messageBus = new ReactiveUI.MessageBus(); + + messageBus.Listen().Subscribe(); + + await Assert.That(messageBus.IsRegistered(typeof(int))).IsTrue(); + } + + /// + /// Tests that IsRegistered returns true after a message is sent. + /// Verifies that sending a message registers the type in the message bus. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task IsRegistered_AfterSendMessage_ReturnsTrue() + { + var messageBus = new ReactiveUI.MessageBus(); + + messageBus.SendMessage("Test"); + + await Assert.That(messageBus.IsRegistered(typeof(string))).IsTrue(); + } + + /// + /// Tests that IsRegistered returns false before any messages are sent. + /// Verifies initial state of message bus registration tracking. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task IsRegistered_BeforeMessages_ReturnsFalse() + { + var messageBus = new ReactiveUI.MessageBus(); + + await Assert.That(messageBus.IsRegistered(typeof(string))).IsFalse(); + } + + /// + /// Tests that IsRegistered returns false for different types. + /// Verifies that registration is type-specific. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task IsRegistered_DifferentType_ReturnsFalse() + { + var messageBus = new ReactiveUI.MessageBus(); + + messageBus.SendMessage(42); + + using (Assert.Multiple()) + { + await Assert.That(messageBus.IsRegistered(typeof(int))).IsTrue(); + await Assert.That(messageBus.IsRegistered(typeof(string))).IsFalse(); + } + } + + /// + /// Tests that IsRegistered with contract distinguishes between different contracts. + /// Verifies that registration checking respects contract boundaries. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task IsRegistered_WithContract_DistinguishesContracts() + { + var messageBus = new ReactiveUI.MessageBus(); + + messageBus.SendMessage("Test", "Contract1"); + + using (Assert.Multiple()) + { + await Assert.That(messageBus.IsRegistered(typeof(string), "Contract1")).IsTrue(); + await Assert.That(messageBus.IsRegistered(typeof(string), "Contract2")).IsFalse(); + await Assert.That(messageBus.IsRegistered(typeof(string))).IsFalse(); + } + } + + /// + /// Tests that Listen observable is cold until subscribed. + /// Verifies that Listen doesn't cause side effects until subscription. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Listen_ColdObservable_NoSideEffects() + { + var messageBus = new ReactiveUI.MessageBus(); + + var observable = messageBus.Listen(); + + await Assert.That(messageBus.IsRegistered(typeof(string))).IsFalse(); + + observable.Subscribe(); + + await Assert.That(messageBus.IsRegistered(typeof(string))).IsTrue(); + } + + /// + /// Tests that messages sent before subscription are not received by Listen. + /// Verifies that Listen uses Skip(1) to exclude historical messages. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Listen_MessagesSentBeforeSubscription_AreNotReceived() + { + var messageBus = new ReactiveUI.MessageBus(); + + messageBus.SendMessage("Before1"); + messageBus.SendMessage("Before2"); + + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + messageBus.SendMessage("After"); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(1); + await Assert.That(messages[0]).IsEqualTo("After"); + } + } + + /// + /// Tests that Listen does not receive the initial default value. + /// Verifies that Listen skips the initial BehaviorSubject value and only receives explicit messages. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Listen_SkipsInitialValue() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages).Subscribe(); + + await Assert.That(messages).IsEmpty(); + + messageBus.SendMessage(42); + await Assert.That(messages).Count().IsEqualTo(1); + await Assert.That(messages[0]).IsEqualTo(42); + } + + /// + /// Tests that unsubscribing from Listen stops receiving messages. + /// Verifies proper subscription disposal and cleanup. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task Listen_Unsubscribe_StopsReceivingMessages() + { + var messageBus = new ReactiveUI.MessageBus(); + var subscription = messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var messages).Subscribe(); + + messageBus.SendMessage("Before"); + subscription.Dispose(); + messageBus.SendMessage("After"); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(1); + await Assert.That(messages[0]).IsEqualTo("Before"); + } + } + + /// + /// Tests that ListenIncludeLatest receives the latest message sent before subscription. + /// Verifies BehaviorSubject replay behavior of ListenIncludeLatest. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ListenIncludeLatest_MessagesSentBeforeSubscription_ReceivesLatest() + { + var messageBus = new ReactiveUI.MessageBus(); + + messageBus.SendMessage("First"); + messageBus.SendMessage("Second"); + messageBus.SendMessage("Third"); + + messageBus.ListenIncludeLatest().ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var messages).Subscribe(); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(1); + await Assert.That(messages[0]).IsEqualTo("Third"); + } + + messageBus.SendMessage("Fourth"); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(2); + await Assert.That(messages[1]).IsEqualTo("Fourth"); + } + } + + /// + /// Tests that ListenIncludeLatest receives the initial default value. + /// Verifies that ListenIncludeLatest includes the BehaviorSubject's initial value. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ListenIncludeLatest_ReceivesInitialValue() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.ListenIncludeLatest().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + await Assert.That(messages).Count().IsEqualTo(1); + await Assert.That(messages[0]).IsEqualTo(0); + + messageBus.SendMessage(42); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(2); + await Assert.That(messages[0]).IsEqualTo(0); + await Assert.That(messages[1]).IsEqualTo(42); + } + } + + /// + /// Tests that ListenIncludeLatest receives the latest message when subscribing after a message was sent. + /// Verifies the BehaviorSubject replay semantics of ListenIncludeLatest. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ListenIncludeLatest_ReceivesLatestMessage() + { + var messageBus = new ReactiveUI.MessageBus(); + + messageBus.SendMessage("First"); + messageBus.SendMessage("Second"); + + messageBus.ListenIncludeLatest().ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var messages).Subscribe(); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(1); + await Assert.That(messages[0]).IsEqualTo("Second"); + } + } + + /// + /// Tests that disposing the RegisterMessageSource subscription stops sending messages. + /// Verifies that the returned IDisposable properly unsubscribes from the source observable. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RegisterMessageSource_Dispose_StopsSendingMessages() + { + var messageBus = new ReactiveUI.MessageBus(); + var source = new Subject(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + var subscription = messageBus.RegisterMessageSource(source); + + source.OnNext("Before"); + subscription.Dispose(); + source.OnNext("After"); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(1); + await Assert.That(messages[0]).IsEqualTo("Before"); + } + } + + /// + /// Tests that RegisterMessageSource throws on null source. + /// Verifies proper argument validation in RegisterMessageSource. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RegisterMessageSource_NullSource_ThrowsArgumentNullException() + { + var messageBus = new ReactiveUI.MessageBus(); + + await Assert.ThrowsAsync(async () => + { + messageBus.RegisterMessageSource(null!); + await Task.CompletedTask; + }); + } + + /// + /// Tests that RegisterMessageSource handles observable completion. + /// Verifies that completing the source observable unsubscribes cleanly. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RegisterMessageSource_ObservableComplete_UnsubscribesCorrectly() + { + var messageBus = new ReactiveUI.MessageBus(); + var source = new Subject(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + messageBus.RegisterMessageSource(source); + + source.OnNext("Before"); + source.OnCompleted(); + + messageBus.SendMessage("After"); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(2); + await Assert.That(messages[0]).IsEqualTo("Before"); + await Assert.That(messages[1]).IsEqualTo("After"); + } + } + + /// + /// Tests that RegisterMessageSource handles observable errors gracefully. + /// Verifies that errors in the source observable don't break the message bus. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RegisterMessageSource_ObservableError_DoesNotBreakBus() + { + var messageBus = new ReactiveUI.MessageBus(); + var source = new Subject(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + messageBus.RegisterMessageSource(source); + + source.OnNext("Before"); + source.OnError(new InvalidOperationException("Test error")); + + messageBus.SendMessage("After"); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(2); + await Assert.That(messages[0]).IsEqualTo("Before"); + await Assert.That(messages[1]).IsEqualTo("After"); + } + } + + /// + /// Tests that RegisterMessageSource subscribes to the source observable and sends messages. + /// Verifies that RegisterMessageSource properly bridges an observable to the message bus. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RegisterMessageSource_SendsMessagesFromObservable() + { + var messageBus = new ReactiveUI.MessageBus(); + var source = new Subject(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + messageBus.RegisterMessageSource(source); + + source.OnNext("First"); + source.OnNext("Second"); + source.OnNext("Third"); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(3); + await Assert.That(messages[0]).IsEqualTo("First"); + await Assert.That(messages[1]).IsEqualTo("Second"); + await Assert.That(messages[2]).IsEqualTo("Third"); + } + } + + /// + /// Tests that RegisterMessageSource with contract sends to the correct listeners. + /// Verifies that contract parameter is properly passed through when registering a message source. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RegisterMessageSource_WithContract_SendsToCorrectListeners() + { + var messageBus = new ReactiveUI.MessageBus(); + var source = new Subject(); + messageBus.Listen("MyContract").ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var contractMessages).Subscribe(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var noContractMessages) + .Subscribe(); + + messageBus.RegisterMessageSource(source, "MyContract"); + + source.OnNext(1); + source.OnNext(2); + + using (Assert.Multiple()) + { + await Assert.That(contractMessages).Count().IsEqualTo(2); + await Assert.That(contractMessages[0]).IsEqualTo(1); + await Assert.That(contractMessages[1]).IsEqualTo(2); + await Assert.That(noContractMessages).IsEmpty(); + } + } + + /// + /// Tests that RegisterScheduler affects message delivery scheduler. + /// Verifies that messages are delivered on the registered scheduler. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RegisterScheduler_AffectsMessageDelivery() + { + var messageBus = new ReactiveUI.MessageBus(); + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + messageBus.RegisterScheduler(scheduler); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + messageBus.SendMessage("Test"); + + await Assert.That(messages).IsEmpty(); + + scheduler.AdvanceBy(TimeSpan.FromTicks(1)); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(1); + await Assert.That(messages[0]).IsEqualTo("Test"); + } + } + + /// + /// Tests that multiple RegisterScheduler operations on the same type-contract overwrite previous registrations. + /// Verifies that scheduler registration follows last-wins semantics. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RegisterScheduler_Multiple_OverwritesPrevious() + { + var messageBus = new ReactiveUI.MessageBus(); + var scheduler1 = TestContext.Current.GetVirtualTimeScheduler(); + var scheduler2 = new ReactiveUI.Tests.Utilities.Schedulers.VirtualTimeScheduler(); + + messageBus.RegisterScheduler(scheduler1); + messageBus.RegisterScheduler(scheduler2); + + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + messageBus.SendMessage("Test"); + + await Assert.That(messages).IsEmpty(); + + scheduler1.AdvanceBy(TimeSpan.FromTicks(1)); + await Assert.That(messages).IsEmpty(); + + scheduler2.AdvanceBy(TimeSpan.FromTicks(1)); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(1); + await Assert.That(messages[0]).IsEqualTo("Test"); + } + } + + /// + /// Tests that RegisterScheduler with contract only affects messages with that contract. + /// Verifies that scheduler registration is scoped to specific type-contract combinations. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RegisterScheduler_WithContract_OnlyAffectsContract() + { + var messageBus = new ReactiveUI.MessageBus(); + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + messageBus.RegisterScheduler(scheduler, "TestContract"); + messageBus.Listen("TestContract").ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var contractMessages).Subscribe(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var normalMessages) + .Subscribe(); + + messageBus.SendMessage("Contract", "TestContract"); + messageBus.SendMessage("Normal"); + + using (Assert.Multiple()) + { + await Assert.That(contractMessages).IsEmpty(); + await Assert.That(normalMessages).Count().IsEqualTo(1); + await Assert.That(normalMessages[0]).IsEqualTo("Normal"); + } + + scheduler.AdvanceBy(TimeSpan.FromTicks(1)); + + using (Assert.Multiple()) + { + await Assert.That(contractMessages).Count().IsEqualTo(1); + await Assert.That(contractMessages[0]).IsEqualTo("Contract"); + } + } + + /// + /// Tests that complex objects work as messages. + /// Verifies support for custom class types as messages. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task SendMessage_ComplexObject_WorksCorrectly() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + var msg1 = new TestMessage { Id = 1, Text = "First" }; + var msg2 = new TestMessage { Id = 2, Text = "Second" }; + + messageBus.SendMessage(msg1); + messageBus.SendMessage(msg2); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(2); + await Assert.That(messages[0]).IsEqualTo(msg1); + await Assert.That(messages[1]).IsEqualTo(msg2); + await Assert.That(messages[0].Id).IsEqualTo(1); + await Assert.That(messages[0].Text).IsEqualTo("First"); + await Assert.That(messages[1].Id).IsEqualTo(2); + await Assert.That(messages[1].Text).IsEqualTo("Second"); + } + } + + /// + /// Tests concurrent SendMessage calls from multiple threads. + /// Verifies thread-safety of message bus operations. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task SendMessage_ConcurrentCalls_ThreadSafe() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages).Subscribe(); + + var tasks = Enumerable.Range(0, 100).Select(i => Task.Run(() => messageBus.SendMessage(i))).ToArray(); + + await Task.WhenAll(tasks); + + await Assert.That(messages).Count().IsEqualTo(100); + } + + /// + /// Tests that different message types are independent. + /// Verifies that messages of different types are delivered to their respective subscribers only. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task SendMessage_DifferentTypes_AreIndependent() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var stringMessages) + .Subscribe(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var intMessages) + .Subscribe(); + + messageBus.SendMessage("Hello"); + messageBus.SendMessage(42); + messageBus.SendMessage("World"); + messageBus.SendMessage(100); + + using (Assert.Multiple()) + { + await Assert.That(stringMessages).Count().IsEqualTo(2); + await Assert.That(stringMessages[0]).IsEqualTo("Hello"); + await Assert.That(stringMessages[1]).IsEqualTo("World"); + await Assert.That(intMessages).Count().IsEqualTo(2); + await Assert.That(intMessages[0]).IsEqualTo(42); + await Assert.That(intMessages[1]).IsEqualTo(100); + } + } + + /// + /// Tests that null contract and empty string contract are different. + /// Verifies that null and empty string are treated as distinct contract values. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task SendMessage_Listen_NullVsEmptyContract_AreDifferent() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var nullMessages) + .Subscribe(); + messageBus.Listen(string.Empty).ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var emptyMessages).Subscribe(); + + messageBus.SendMessage("Null"); + messageBus.SendMessage("Empty", string.Empty); + + using (Assert.Multiple()) + { + await Assert.That(nullMessages).Count().IsEqualTo(1); + await Assert.That(nullMessages[0]).IsEqualTo("Null"); + await Assert.That(emptyMessages).Count().IsEqualTo(1); + await Assert.That(emptyMessages[0]).IsEqualTo("Empty"); + } + } + + /// + /// Tests that SendMessage and Listen work together for basic message passing. + /// Verifies that messages sent via SendMessage are received by Listen subscribers. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task SendMessage_Listen_ReceivesMessage() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var messages) + .Subscribe(); + + messageBus.SendMessage("Hello"); + messageBus.SendMessage("World"); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(2); + await Assert.That(messages[0]).IsEqualTo("Hello"); + await Assert.That(messages[1]).IsEqualTo("World"); + } + } + + /// + /// Tests that contracts distinguish between messages of the same type. + /// Verifies that messages with different contracts are delivered to separate subscribers. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task SendMessage_Listen_WithContract_DistinguishesMessages() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.Listen("Contract1").ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var messages1).Subscribe(); + messageBus.Listen("Contract2").ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var messages2).Subscribe(); + + messageBus.SendMessage("Message1", "Contract1"); + messageBus.SendMessage("Message2", "Contract2"); + messageBus.SendMessage("Message3", "Contract1"); + + using (Assert.Multiple()) + { + await Assert.That(messages1).Count().IsEqualTo(2); + await Assert.That(messages1[0]).IsEqualTo("Message1"); + await Assert.That(messages1[1]).IsEqualTo("Message3"); + await Assert.That(messages2).Count().IsEqualTo(1); + await Assert.That(messages2[0]).IsEqualTo("Message2"); + } + } + + /// + /// Tests that multiple subscribers receive the same message. + /// Verifies that the message bus supports multiple concurrent subscribers. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task SendMessage_MultipleSubscribers_AllReceiveMessage() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var subscriber1) + .Subscribe(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var subscriber2) + .Subscribe(); + messageBus.Listen().ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var subscriber3) + .Subscribe(); + + messageBus.SendMessage(42); + + using (Assert.Multiple()) + { + await Assert.That(subscriber1).Count().IsEqualTo(1); + await Assert.That(subscriber1[0]).IsEqualTo(42); + await Assert.That(subscriber2).Count().IsEqualTo(1); + await Assert.That(subscriber2[0]).IsEqualTo(42); + await Assert.That(subscriber3).Count().IsEqualTo(1); + await Assert.That(subscriber3[0]).IsEqualTo(42); + } + } + + /// + /// Tests that nullable value types work correctly. + /// Verifies support for Nullable<T> message types. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task SendMessage_NullableValueType_WorksCorrectly() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.RegisterScheduler(ImmediateScheduler.Instance); + var messages = new List(); + messageBus.Listen().Subscribe(messages.Add); + + messageBus.SendMessage(42); + messageBus.SendMessage(null); + messageBus.SendMessage(100); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(3); + await Assert.That(messages[0]).IsEqualTo(42); + await Assert.That(messages[1]).IsNull(); + await Assert.That(messages[2]).IsEqualTo(100); + } + } + + /// + /// Tests that reference type null values work correctly. + /// Verifies support for null reference type messages. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task SendMessage_NullReferenceType_WorksCorrectly() + { + var messageBus = new ReactiveUI.MessageBus(); + messageBus.RegisterScheduler(ImmediateScheduler.Instance); + var messages = new List(); + messageBus.Listen().Subscribe(messages.Add); + + messageBus.SendMessage("Hello"); + messageBus.SendMessage(null); + messageBus.SendMessage("World"); + + using (Assert.Multiple()) + { + await Assert.That(messages).Count().IsEqualTo(3); + await Assert.That(messages[0]).IsEqualTo("Hello"); + await Assert.That(messages[1]).IsNull(); + await Assert.That(messages[2]).IsEqualTo("World"); + } + } + + /// + /// Test message class for complex object testing. + /// + private class TestMessage + { + /// + /// Gets or sets the message identifier. + /// + public int Id { get; set; } + + /// + /// Gets or sets the message text content. + /// + public string? Text { get; set; } + } +} diff --git a/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverAOTExtensionsTests.cs b/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverAOTExtensionsTests.cs index c83a776665..f899d36cb1 100644 --- a/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverAOTExtensionsTests.cs +++ b/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverAOTExtensionsTests.cs @@ -6,21 +6,21 @@ namespace ReactiveUI.Tests.Mixins; /// -/// Tests for the class. -/// These tests verify the AOT-friendly registration helpers. +/// Tests for the class. +/// These tests verify the AOT-friendly registration helpers. /// public class MutableDependencyResolverAOTExtensionsTests { /// - /// Verifies that RegisterViewForViewModelAOT registers a transient view. + /// Verifies that RegisterSingletonViewForViewModelAOT registers a singleton view. /// /// A Task representing the asynchronous test operation. [Test] - public async Task RegisterViewForViewModelAOT_RegistersTransientView() + public async Task RegisterSingletonViewForViewModelAOT_RegistersSingletonView() { using var resolver = new ModernDependencyResolver(); - MutableDependencyResolverAOTExtensions.RegisterViewForViewModelAOT(resolver); + resolver.RegisterSingletonViewForViewModelAOT(); var view1 = resolver.GetService>(); var view2 = resolver.GetService>(); @@ -29,58 +29,52 @@ public async Task RegisterViewForViewModelAOT_RegistersTransientView() { await Assert.That(view1).IsNotNull(); await Assert.That(view1).IsTypeOf(); - await Assert.That(view2).IsNotNull(); - await Assert.That(view1).IsNotSameReferenceAs(view2); + await Assert.That(view1).IsSameReferenceAs(view2); } } /// - /// Verifies that RegisterViewForViewModelAOT registers view with contract. + /// Verifies that RegisterSingletonViewForViewModelAOT returns the resolver for chaining. /// /// A Task representing the asynchronous test operation. [Test] - public async Task RegisterViewForViewModelAOT_WithContract_RegistersViewWithContract() + public async Task RegisterSingletonViewForViewModelAOT_ReturnsResolverForChaining() { using var resolver = new ModernDependencyResolver(); - MutableDependencyResolverAOTExtensions.RegisterViewForViewModelAOT(resolver, "MyContract"); - - var view = resolver.GetService>("MyContract"); + var result = resolver.RegisterSingletonViewForViewModelAOT(); - using (Assert.Multiple()) - { - await Assert.That(view).IsNotNull(); - await Assert.That(view).IsTypeOf(); - } + await Assert.That(result).IsSameReferenceAs(resolver); } /// - /// Verifies that RegisterViewForViewModelAOT throws ArgumentNullException when resolver is null. + /// Verifies that RegisterSingletonViewForViewModelAOT throws ArgumentNullException when resolver is null. /// /// A Task representing the asynchronous test operation. [Test] - public async Task RegisterViewForViewModelAOT_ThrowsArgumentNullException_WhenResolverIsNull() + public async Task RegisterSingletonViewForViewModelAOT_ThrowsArgumentNullException_WhenResolverIsNull() { IMutableDependencyResolver? resolver = null; - var exception = await Assert.That(() => MutableDependencyResolverAOTExtensions.RegisterViewForViewModelAOT(resolver!)) + var exception = await Assert + .That(() => resolver!.RegisterSingletonViewForViewModelAOT()) .Throws(); await Assert.That(exception).IsNotNull(); } /// - /// Verifies that RegisterSingletonViewForViewModelAOT registers a singleton view. + /// Verifies that RegisterSingletonViewForViewModelAOT registers singleton view with contract. /// /// A Task representing the asynchronous test operation. [Test] - public async Task RegisterSingletonViewForViewModelAOT_RegistersSingletonView() + public async Task RegisterSingletonViewForViewModelAOT_WithContract_RegistersViewWithContract() { using var resolver = new ModernDependencyResolver(); - MutableDependencyResolverAOTExtensions.RegisterSingletonViewForViewModelAOT(resolver); + resolver.RegisterSingletonViewForViewModelAOT("SingletonContract"); - var view1 = resolver.GetService>(); - var view2 = resolver.GetService>(); + var view1 = resolver.GetService>("SingletonContract"); + var view2 = resolver.GetService>("SingletonContract"); using (Assert.Multiple()) { @@ -91,71 +85,74 @@ public async Task RegisterSingletonViewForViewModelAOT_RegistersSingletonView() } /// - /// Verifies that RegisterSingletonViewForViewModelAOT registers singleton view with contract. + /// Verifies that RegisterViewForViewModelAOT registers a transient view. /// /// A Task representing the asynchronous test operation. [Test] - public async Task RegisterSingletonViewForViewModelAOT_WithContract_RegistersViewWithContract() + public async Task RegisterViewForViewModelAOT_RegistersTransientView() { using var resolver = new ModernDependencyResolver(); - MutableDependencyResolverAOTExtensions.RegisterSingletonViewForViewModelAOT(resolver, "SingletonContract"); + resolver.RegisterViewForViewModelAOT(); - var view1 = resolver.GetService>("SingletonContract"); - var view2 = resolver.GetService>("SingletonContract"); + var view1 = resolver.GetService>(); + var view2 = resolver.GetService>(); using (Assert.Multiple()) { await Assert.That(view1).IsNotNull(); await Assert.That(view1).IsTypeOf(); - await Assert.That(view1).IsSameReferenceAs(view2); + await Assert.That(view2).IsNotNull(); + await Assert.That(view1).IsNotSameReferenceAs(view2); } } /// - /// Verifies that RegisterSingletonViewForViewModelAOT throws ArgumentNullException when resolver is null. + /// Verifies that RegisterViewForViewModelAOT returns the resolver for chaining. /// /// A Task representing the asynchronous test operation. [Test] - public async Task RegisterSingletonViewForViewModelAOT_ThrowsArgumentNullException_WhenResolverIsNull() + public async Task RegisterViewForViewModelAOT_ReturnsResolverForChaining() { - IMutableDependencyResolver? resolver = null; + using var resolver = new ModernDependencyResolver(); - var exception = await Assert.That(() => MutableDependencyResolverAOTExtensions.RegisterSingletonViewForViewModelAOT(resolver!)) - .Throws(); - await Assert.That(exception).IsNotNull(); + var result = resolver.RegisterViewForViewModelAOT(); + + await Assert.That(result).IsSameReferenceAs(resolver); } /// - /// Verifies that RegisterViewForViewModelAOT returns the resolver for chaining. + /// Verifies that RegisterViewForViewModelAOT throws ArgumentNullException when resolver is null. /// /// A Task representing the asynchronous test operation. [Test] - public async Task RegisterViewForViewModelAOT_ReturnsResolverForChaining() + public async Task RegisterViewForViewModelAOT_ThrowsArgumentNullException_WhenResolverIsNull() { - using var resolver = new ModernDependencyResolver(); - - var result = MutableDependencyResolverAOTExtensions.RegisterViewForViewModelAOT(resolver); + IMutableDependencyResolver? resolver = null; - await Assert.That(result).IsSameReferenceAs(resolver); + var exception = await Assert.That(() => resolver!.RegisterViewForViewModelAOT()) + .Throws(); + await Assert.That(exception).IsNotNull(); } /// - /// Verifies that RegisterSingletonViewForViewModelAOT returns the resolver for chaining. + /// Verifies that RegisterViewForViewModelAOT registers view with contract. /// /// A Task representing the asynchronous test operation. [Test] - public async Task RegisterSingletonViewForViewModelAOT_ReturnsResolverForChaining() + public async Task RegisterViewForViewModelAOT_WithContract_RegistersViewWithContract() { using var resolver = new ModernDependencyResolver(); - var result = MutableDependencyResolverAOTExtensions.RegisterSingletonViewForViewModelAOT(resolver); + resolver.RegisterViewForViewModelAOT("MyContract"); - await Assert.That(result).IsSameReferenceAs(resolver); - } + var view = resolver.GetService>("MyContract"); - private class TestViewModel : ReactiveObject - { + using (Assert.Multiple()) + { + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsTypeOf(); + } } private class TestView : IViewFor @@ -168,4 +165,8 @@ private class TestView : IViewFor set => ViewModel = (TestViewModel?)value; } } + + private class TestViewModel : ReactiveObject + { + } } diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs b/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs similarity index 91% rename from src/tests/ReactiveUI.NonParallel.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs rename to src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs index 5ad29d9655..058480c4ce 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs +++ b/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs @@ -3,35 +3,22 @@ // 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.Infrastructure.StaticState; +using ReactiveUI.Tests.Utilities.AppBuilder; namespace ReactiveUI.Tests.Mixins; /// -/// Tests for MutableDependencyResolverExtensions. +/// Tests for MutableDependencyResolverExtensions. /// [NotInParallel] -public class MutableDependencyResolverExtensionsTests : IDisposable +[TestExecutor] +public class MutableDependencyResolverExtensionsTests { - private RxAppSchedulersScope? _schedulersScope; - - [Before(Test)] - public void SetUp() - { - _schedulersScope = new RxAppSchedulersScope(); - } - - [After(Test)] - public void TearDown() - { - _schedulersScope?.Dispose(); - } - [Test] - public async Task RegisterViewForViewModelRegistersView() + public async Task RegisterSingletonViewForViewModelRegistersSingleton() { var resolver = new ModernDependencyResolver(); - resolver.RegisterViewForViewModel(); + resolver.RegisterSingletonViewForViewModel(); var view = resolver.GetService>(); @@ -43,61 +30,64 @@ public async Task RegisterViewForViewModelRegistersView() } [Test] - public async Task RegisterViewForViewModelWithContractRegistersView() + public async Task RegisterSingletonViewForViewModelReturnsResolver() { var resolver = new ModernDependencyResolver(); - resolver.RegisterViewForViewModel("TestContract"); - - var view = resolver.GetService>("TestContract"); + var result = resolver.RegisterSingletonViewForViewModel(); - using (Assert.Multiple()) - { - await Assert.That(view).IsNotNull(); - await Assert.That(view).IsOfType(typeof(TestView)); - } + await Assert.That(result).IsEqualTo(resolver); } [Test] - public async Task RegisterViewForViewModelReturnsResolver() + public async Task RegisterSingletonViewForViewModelReturnsSameInstance() { var resolver = new ModernDependencyResolver(); - var result = resolver.RegisterViewForViewModel(); + resolver.RegisterSingletonViewForViewModel(); - await Assert.That(result).IsEqualTo(resolver); + var view1 = resolver.GetService>(); + var view2 = resolver.GetService>(); + + using (Assert.Multiple()) + { + await Assert.That(view1).IsNotNull(); + await Assert.That(view2).IsNotNull(); + await Assert.That(ReferenceEquals(view1, view2)).IsTrue(); + } } [Test] - public async Task RegisterViewForViewModelCreatesNewInstanceEachTime() + public async Task RegisterSingletonViewForViewModelSupportsChaining() { var resolver = new ModernDependencyResolver(); - resolver.RegisterViewForViewModel(); + resolver + .RegisterSingletonViewForViewModel() + .RegisterSingletonViewForViewModel(); var view1 = resolver.GetService>(); - var view2 = resolver.GetService>(); + var view2 = resolver.GetService>(); using (Assert.Multiple()) { await Assert.That(view1).IsNotNull(); await Assert.That(view2).IsNotNull(); - await Assert.That(ReferenceEquals(view1, view2)).IsFalse(); } } [Test] - public void RegisterViewForViewModelThrowsOnNullResolver() + public void RegisterSingletonViewForViewModelThrowsOnNullResolver() { IMutableDependencyResolver? resolver = null; Assert.Throws(() => - resolver!.RegisterViewForViewModel()); + resolver!.RegisterSingletonViewForViewModel()); } [Test] - public async Task RegisterSingletonViewForViewModelRegistersSingleton() + public async Task RegisterSingletonViewForViewModelWithContractRegistersSingleton() { var resolver = new ModernDependencyResolver(); - resolver.RegisterSingletonViewForViewModel(); + resolver.RegisterSingletonViewForViewModel("TestContract"); - var view = resolver.GetService>(); + var view = resolver.GetService>("TestContract"); using (Assert.Multiple()) { @@ -107,52 +97,44 @@ public async Task RegisterSingletonViewForViewModelRegistersSingleton() } [Test] - public async Task RegisterSingletonViewForViewModelWithContractRegistersSingleton() + public async Task RegisterViewForViewModelCreatesNewInstanceEachTime() { var resolver = new ModernDependencyResolver(); - resolver.RegisterSingletonViewForViewModel("TestContract"); + resolver.RegisterViewForViewModel(); - var view = resolver.GetService>("TestContract"); + var view1 = resolver.GetService>(); + var view2 = resolver.GetService>(); using (Assert.Multiple()) { - await Assert.That(view).IsNotNull(); - await Assert.That(view).IsOfType(typeof(TestView)); + await Assert.That(view1).IsNotNull(); + await Assert.That(view2).IsNotNull(); + await Assert.That(ReferenceEquals(view1, view2)).IsFalse(); } } [Test] - public async Task RegisterSingletonViewForViewModelReturnsResolver() - { - var resolver = new ModernDependencyResolver(); - var result = resolver.RegisterSingletonViewForViewModel(); - - await Assert.That(result).IsEqualTo(resolver); - } - - [Test] - public async Task RegisterSingletonViewForViewModelReturnsSameInstance() + public async Task RegisterViewForViewModelRegistersView() { var resolver = new ModernDependencyResolver(); - resolver.RegisterSingletonViewForViewModel(); + resolver.RegisterViewForViewModel(); - var view1 = resolver.GetService>(); - var view2 = resolver.GetService>(); + var view = resolver.GetService>(); using (Assert.Multiple()) { - await Assert.That(view1).IsNotNull(); - await Assert.That(view2).IsNotNull(); - await Assert.That(ReferenceEquals(view1, view2)).IsTrue(); + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsOfType(typeof(TestView)); } } [Test] - public void RegisterSingletonViewForViewModelThrowsOnNullResolver() + public async Task RegisterViewForViewModelReturnsResolver() { - IMutableDependencyResolver? resolver = null; - Assert.Throws(() => - resolver!.RegisterSingletonViewForViewModel()); + var resolver = new ModernDependencyResolver(); + var result = resolver.RegisterViewForViewModel(); + + await Assert.That(result).IsEqualTo(resolver); } [Test] @@ -174,68 +156,67 @@ public async Task RegisterViewForViewModelSupportsChaining() } [Test] - public async Task RegisterSingletonViewForViewModelSupportsChaining() + public void RegisterViewForViewModelThrowsOnNullResolver() + { + IMutableDependencyResolver? resolver = null; + Assert.Throws(() => + resolver!.RegisterViewForViewModel()); + } + + [Test] + public async Task RegisterViewForViewModelWithContractRegistersView() { var resolver = new ModernDependencyResolver(); - resolver - .RegisterSingletonViewForViewModel() - .RegisterSingletonViewForViewModel(); + resolver.RegisterViewForViewModel("TestContract"); - var view1 = resolver.GetService>(); - var view2 = resolver.GetService>(); + var view = resolver.GetService>("TestContract"); using (Assert.Multiple()) { - await Assert.That(view1).IsNotNull(); - await Assert.That(view2).IsNotNull(); + await Assert.That(view).IsNotNull(); + await Assert.That(view).IsOfType(typeof(TestView)); } } - public void Dispose() - { - _schedulersScope?.Dispose(); - _schedulersScope = null; - } - - /// - /// Test view model. - /// - private class TestViewModel : ReactiveObject - { - } - /// - /// Test view. + /// Alternate test view. /// - private class TestView : IViewFor + private class AlternateTestView : IViewFor { - public TestViewModel? ViewModel { get; set; } + public AlternateTestViewModel? ViewModel { get; set; } object? IViewFor.ViewModel { get => ViewModel; - set => ViewModel = value as TestViewModel; + set => ViewModel = value as AlternateTestViewModel; } } /// - /// Alternate test view model. + /// Alternate test view model. /// private class AlternateTestViewModel : ReactiveObject { } /// - /// Alternate test view. + /// Test view. /// - private class AlternateTestView : IViewFor + private class TestView : IViewFor { - public AlternateTestViewModel? ViewModel { get; set; } + public TestViewModel? ViewModel { get; set; } object? IViewFor.ViewModel { get => ViewModel; - set => ViewModel = value as AlternateTestViewModel; + set => ViewModel = value as TestViewModel; } } + + /// + /// Test view model. + /// + private class TestViewModel : ReactiveObject + { + } } diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Mixins/ObservableLoggingMixinTests.cs b/src/tests/ReactiveUI.Tests/Mixins/ObservableLoggingMixinTests.cs similarity index 72% rename from src/tests/ReactiveUI.NonParallel.Tests/Mixins/ObservableLoggingMixinTests.cs rename to src/tests/ReactiveUI.Tests/Mixins/ObservableLoggingMixinTests.cs index 772ee4aff2..170056d934 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Mixins/ObservableLoggingMixinTests.cs +++ b/src/tests/ReactiveUI.Tests/Mixins/ObservableLoggingMixinTests.cs @@ -4,61 +4,39 @@ // See the LICENSE file in the project root for full license information. using System.Globalization; +using ReactiveUI.Tests.Utilities.Logging; namespace ReactiveUI.Tests.Mixins; /// -/// Tests for the class. -/// These tests verify the logging functionality for Observables. +/// Tests for the class. +/// These tests verify the logging functionality for Observables. /// +[NotInParallel] +[TestExecutor] public class ObservableLoggingMixinTests { - private LoggingRegistrationScope? _loggingScope; - - [Before(Test)] - public void Setup() - { - _loggingScope = new LoggingRegistrationScope(); - } - - [After(Test)] - public void Teardown() - { - _loggingScope?.Dispose(); - _loggingScope = null; - } - /// - /// Verifies that TestEnableLogger captures Info and Warn messages directly. + /// Verifies that using IEnableLogger as a local variable works. /// /// A Task representing the asynchronous test operation. [Test] - public async Task TestEnableLogger_CapturesInfoAndWarnMessages() + public async Task LocalInterfaceVariable_WorksWithIEnableLogger() { - var logger = new TestEnableLogger(_loggingScope!.Logger); - - // Call Info and Warn directly - logger.Log().Info(CultureInfo.InvariantCulture, "Test message {0}", "arg1"); - logger.Log().Warn(new InvalidOperationException(), "Error occurred"); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); - await Assert.That(logger.InfoCount).IsEqualTo(1); - await Assert.That(logger.WarnCount).IsEqualTo(1); - } + var logger = new TestEnableLogger(loggerInstance); - /// - /// Verifies that TestEnableLogger captures Info messages with 2 generic arguments (like ObservableLoggingMixin uses). - /// - /// A Task representing the asynchronous test operation. - [Test] - public async Task TestEnableLogger_CapturesGenericInfoWithTwoArguments() - { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var testLogger = (TestEnableLogger)logger; var subject = new Subject(); + // Use interface variable in Do() + var message = "Test"; var logged = subject.Do( - x => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnNext: {1}", "Test", x), - ex => logger.Log().Warn(ex, "Test OnError"), - () => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnCompleted", "Test")); + x => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnNext: {1}", message, x), + ex => logger.Log().Warn(ex, message + " OnError"), + () => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnCompleted", message)); var values = new List(); logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); @@ -68,49 +46,71 @@ public async Task TestEnableLogger_CapturesGenericInfoWithTwoArguments() subject.OnCompleted(); await Assert.That(values).IsEquivalentTo([1, 2]); - await Assert.That(logger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted + await Assert.That(testLogger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted } /// - /// Verifies that the Log extension method works with TestEnableLogger. + /// Verifies that logged observable completes normally without errors. /// /// A Task representing the asynchronous test operation. [Test] - public async Task LogExtensionMethod_WorksWithTestEnableLogger() + public async Task Log_CompletesNormally_WithoutErrors() + { + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); + var values = Observable.Range(1, 5); + + var logged = values.Log(logger, "Range"); + + var results = new List(); + var completed = false; + logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(results.Add, () => completed = true); + + await Assert.That(results).IsEquivalentTo([1, 2, 3, 4, 5]); + await Assert.That(completed).IsTrue(); + await Assert.That(logger.InfoCount).IsGreaterThanOrEqualTo(6); // 5 OnNext + 1 OnCompleted + } + + /// + /// Verifies that Log extension method logs errors. + /// + /// A Task representing the asynchronous test operation. + [Test] + public async Task Log_LogsErrors() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); - // This is the actual Log extension method - var logged = ObservableLoggingMixin.Log(subject, logger, "Test"); + var logged = subject.Log(logger, "Test"); - var values = new List(); - logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); + var errorCaught = false; + logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(_ => { }, _ => errorCaught = true); - subject.OnNext(1); - subject.OnNext(2); - subject.OnCompleted(); + subject.OnError(new InvalidOperationException("Test error")); - await Assert.That(values).IsEquivalentTo([1, 2]); - await Assert.That(logger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted + await Assert.That(errorCaught).IsTrue(); + await Assert.That(logger.WarnCount).IsGreaterThanOrEqualTo(1); } /// - /// Verifies that manually inlining the Log extension method logic works. + /// Verifies that Log extension method logs OnNext, OnError, and OnCompleted events. /// /// A Task representing the asynchronous test operation. [Test] - public async Task ManualInline_WorksWithTestEnableLogger() + public async Task Log_LogsOnNextOnErrorAndOnCompleted() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); - // Manually inline what Log() does - var message = "Test"; - var logged = subject.Do( - x => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnNext: {1}", message, x), - ex => logger.Log().Warn(ex, message + " OnError"), - () => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnCompleted", message)); + var logged = subject.Log(logger, "Test"); var values = new List(); logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); @@ -120,71 +120,73 @@ public async Task ManualInline_WorksWithTestEnableLogger() subject.OnCompleted(); await Assert.That(values).IsEquivalentTo([1, 2]); - await Assert.That(logger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted + await Assert.That(logger.InfoCount).IsGreaterThanOrEqualTo(3); // 2 OnNext + 1 OnCompleted } /// - /// Verifies that using IEnableLogger as a local variable works. + /// Verifies that Log extension method uses empty message when null is provided. /// /// A Task representing the asynchronous test operation. [Test] - public async Task LocalInterfaceVariable_WorksWithIEnableLogger() + public async Task Log_WithNullMessage_UsesEmptyString() { - var logger = new TestEnableLogger(_loggingScope!.Logger); - var testLogger = (TestEnableLogger)logger; + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); - // Use interface variable in Do() - var message = "Test"; - var logged = subject.Do( - x => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnNext: {1}", message, x), - ex => logger.Log().Warn(ex, message + " OnError"), - () => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnCompleted", message)); + var logged = subject.Log(logger); var values = new List(); logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); subject.OnNext(1); - subject.OnNext(2); subject.OnCompleted(); - await Assert.That(values).IsEquivalentTo([1, 2]); - await Assert.That(testLogger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted + await Assert.That(values).Contains(1); + await Assert.That(logger.InfoCount).IsGreaterThanOrEqualTo(2); } /// - /// Verifies that a non-generic helper method works with IEnableLogger. + /// Verifies that Log extension method with stringifier uses custom string conversion. /// /// A Task representing the asynchronous test operation. [Test] - public async Task NonGenericHelper_WorksWithIEnableLogger() + public async Task Log_WithStringifier_UsesCustomConversion() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); - var logged = LogNonGeneric(subject, logger, "Test"); + var logged = subject.Log(logger, "Test", x => $"Value: {x}"); var values = new List(); logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); - subject.OnNext(1); - subject.OnNext(2); + subject.OnNext(42); subject.OnCompleted(); - await Assert.That(values).IsEquivalentTo([1, 2]); - await Assert.That(logger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted + await Assert.That(values).Contains(42); + await Assert.That(logger.InfoCount).IsGreaterThanOrEqualTo(2); } /// - /// Verifies that Log extension method logs OnNext, OnError, and OnCompleted events. + /// Verifies that the Log extension method works with TestEnableLogger. /// /// A Task representing the asynchronous test operation. [Test] - public async Task Log_LogsOnNextOnErrorAndOnCompleted() + public async Task LogExtensionMethod_WorksWithTestEnableLogger() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); + // This is the actual Log extension method var logged = subject.Log(logger, "Test"); var values = new List(); @@ -195,104 +197,128 @@ public async Task Log_LogsOnNextOnErrorAndOnCompleted() subject.OnCompleted(); await Assert.That(values).IsEquivalentTo([1, 2]); - await Assert.That(logger.InfoCount).IsGreaterThanOrEqualTo(3); // 2 OnNext + 1 OnCompleted + await Assert.That(logger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted } /// - /// Verifies that Log extension method logs errors. + /// Verifies that LoggedCatch catches exceptions and returns next observable. /// /// A Task representing the asynchronous test operation. [Test] - public async Task Log_LogsErrors() + public async Task LoggedCatch_CatchesExceptionAndReturnsNext() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); + var fallback = Observable.Return(99); - var logged = subject.Log(logger, "Test"); + var caught = subject.LoggedCatch(logger, fallback, "Error occurred"); - var errorCaught = false; - logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(_ => { }, _ => errorCaught = true); + var values = new List(); + caught.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); - subject.OnError(new InvalidOperationException("Test error")); + subject.OnError(new InvalidOperationException()); - await Assert.That(errorCaught).IsTrue(); + await Assert.That(values).Contains(99); await Assert.That(logger.WarnCount).IsGreaterThanOrEqualTo(1); } /// - /// Verifies that Log extension method with stringifier uses custom string conversion. + /// Verifies that LoggedCatch with exception func passes exception to next factory. /// /// A Task representing the asynchronous test operation. [Test] - public async Task Log_WithStringifier_UsesCustomConversion() + public async Task LoggedCatch_WithExceptionFunc_PassesExceptionToFactory() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); + Exception? capturedEx = null; - var logged = subject.Log(logger, "Test", x => $"Value: {x}"); + var caught = subject.LoggedCatch( + logger, + ex => + { + capturedEx = ex; + return Observable.Return(100); + }); var values = new List(); - logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); + caught.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); - subject.OnNext(42); - subject.OnCompleted(); + var thrownException = new InvalidOperationException("Test"); + subject.OnError(thrownException); - await Assert.That(values).Contains(42); - await Assert.That(logger.InfoCount).IsGreaterThanOrEqualTo(2); + await Assert.That(capturedEx).IsSameReferenceAs(thrownException); + await Assert.That(values).Contains(100); } /// - /// Verifies that Log extension method uses empty message when null is provided. + /// Verifies that LoggedCatch with exception type catches specific exceptions. /// /// A Task representing the asynchronous test operation. [Test] - public async Task Log_WithNullMessage_UsesEmptyString() + public async Task LoggedCatch_WithExceptionType_CatchesSpecificException() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); - var logged = subject.Log(logger, null); + var caught = subject.LoggedCatch( + logger, + ex => Observable.Return(42), + "Specific error"); var values = new List(); - logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); + caught.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); - subject.OnNext(1); - subject.OnCompleted(); + subject.OnError(new InvalidOperationException()); - await Assert.That(values).Contains(1); - await Assert.That(logger.InfoCount).IsGreaterThanOrEqualTo(2); + await Assert.That(values).Contains(42); + await Assert.That(logger.WarnCount).IsGreaterThanOrEqualTo(1); } /// - /// Verifies that LoggedCatch catches exceptions and returns next observable. + /// Verifies that LoggedCatch uses empty string for null message. /// /// A Task representing the asynchronous test operation. [Test] - public async Task LoggedCatch_CatchesExceptionAndReturnsNext() + public async Task LoggedCatch_WithNullMessage_UsesEmptyString() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); - var fallback = Observable.Return(99); - var caught = subject.LoggedCatch(logger, fallback, "Error occurred"); + var caught = subject.LoggedCatch(logger, Observable.Return(1)); var values = new List(); caught.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); subject.OnError(new InvalidOperationException()); - await Assert.That(values).Contains(99); + await Assert.That(values).Contains(1); await Assert.That(logger.WarnCount).IsGreaterThanOrEqualTo(1); } /// - /// Verifies that LoggedCatch uses default observable when next is null. + /// Verifies that LoggedCatch uses default observable when next is null. /// /// A Task representing the asynchronous test operation. [Test] public async Task LoggedCatch_WithNullNext_UsesDefault() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); var caught = subject.LoggedCatch(logger, null, "Error"); @@ -305,102 +331,113 @@ public async Task LoggedCatch_WithNullNext_UsesDefault() } /// - /// Verifies that LoggedCatch uses empty string for null message. + /// Verifies that manually inlining the Log extension method logic works. /// /// A Task representing the asynchronous test operation. [Test] - public async Task LoggedCatch_WithNullMessage_UsesEmptyString() + public async Task ManualInline_WorksWithTestEnableLogger() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); - var caught = subject.LoggedCatch(logger, Observable.Return(1), null); + // Manually inline what Log() does + var message = "Test"; + var logged = subject.Do( + x => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnNext: {1}", message, x), + ex => logger.Log().Warn(ex, message + " OnError"), + () => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnCompleted", message)); var values = new List(); - caught.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); + logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); - subject.OnError(new InvalidOperationException()); + subject.OnNext(1); + subject.OnNext(2); + subject.OnCompleted(); - await Assert.That(values).Contains(1); - await Assert.That(logger.WarnCount).IsGreaterThanOrEqualTo(1); + await Assert.That(values).IsEquivalentTo([1, 2]); + await Assert.That(logger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted } /// - /// Verifies that LoggedCatch with exception type catches specific exceptions. + /// Verifies that a non-generic helper method works with IEnableLogger. /// /// A Task representing the asynchronous test operation. [Test] - public async Task LoggedCatch_WithExceptionType_CatchesSpecificException() + public async Task NonGenericHelper_WorksWithIEnableLogger() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); - var caught = subject.LoggedCatch( - logger, - ex => Observable.Return(42), - "Specific error"); + var logged = LogNonGeneric(subject, logger, "Test"); var values = new List(); - caught.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); + logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); - subject.OnError(new InvalidOperationException()); + subject.OnNext(1); + subject.OnNext(2); + subject.OnCompleted(); - await Assert.That(values).Contains(42); - await Assert.That(logger.WarnCount).IsGreaterThanOrEqualTo(1); + await Assert.That(values).IsEquivalentTo([1, 2]); + await Assert.That(logger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted } /// - /// Verifies that LoggedCatch with exception func passes exception to next factory. + /// Verifies that TestEnableLogger captures Info messages with 2 generic arguments (like ObservableLoggingMixin uses). /// /// A Task representing the asynchronous test operation. [Test] - public async Task LoggedCatch_WithExceptionFunc_PassesExceptionToFactory() + public async Task TestEnableLogger_CapturesGenericInfoWithTwoArguments() { - var logger = new TestEnableLogger(_loggingScope!.Logger); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); + + var logger = new TestEnableLogger(loggerInstance); var subject = new Subject(); - Exception? capturedEx = null; - var caught = subject.LoggedCatch( - logger, - ex => - { - capturedEx = ex; - return Observable.Return(100); - }); + var logged = subject.Do( + x => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnNext: {1}", "Test", x), + ex => logger.Log().Warn(ex, "Test OnError"), + () => logger.Log().Info(CultureInfo.InvariantCulture, "{0} OnCompleted", "Test")); var values = new List(); - caught.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); + logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(values.Add); - var thrownException = new InvalidOperationException("Test"); - subject.OnError(thrownException); + subject.OnNext(1); + subject.OnNext(2); + subject.OnCompleted(); - await Assert.That(capturedEx).IsSameReferenceAs(thrownException); - await Assert.That(values).Contains(100); + await Assert.That(values).IsEquivalentTo([1, 2]); + await Assert.That(logger.InfoCount).IsEqualTo(3); // 2 OnNext + 1 OnCompleted } /// - /// Verifies that logged observable completes normally without errors. + /// Verifies that TestEnableLogger captures Info and Warn messages directly. /// /// A Task representing the asynchronous test operation. [Test] - public async Task Log_CompletesNormally_WithoutErrors() + public async Task TestEnableLogger_CapturesInfoAndWarnMessages() { - var logger = new TestEnableLogger(_loggingScope!.Logger); - var values = Observable.Range(1, 5); + var loggerInstance = TestContext.Current?.GetTestLogger(); + await Assert.That(loggerInstance).IsNotNull(); - var logged = values.Log(logger, "Range"); + var logger = new TestEnableLogger(loggerInstance); - var results = new List(); - var completed = false; - logged.ObserveOn(ImmediateScheduler.Instance).Subscribe(results.Add, () => completed = true); + // Call Info and Warn directly + logger.Log().Info(CultureInfo.InvariantCulture, "Test message {0}", "arg1"); + logger.Log().Warn(new InvalidOperationException(), "Error occurred"); - await Assert.That(results).IsEquivalentTo([1, 2, 3, 4, 5]); - await Assert.That(completed).IsTrue(); - await Assert.That(logger.InfoCount).IsGreaterThanOrEqualTo(6); // 5 OnNext + 1 OnCompleted + await Assert.That(logger.InfoCount).IsEqualTo(1); + await Assert.That(logger.WarnCount).IsEqualTo(1); } /// - /// Helper method that takes IEnableLogger directly (not generic) to test if that works. + /// Helper method that takes IEnableLogger directly (not generic) to test if that works. /// private static IObservable LogNonGeneric(IObservable source, IEnableLogger logObject, string message) { @@ -411,7 +448,7 @@ private static IObservable LogNonGeneric(IObservable source, IEnableLog () => logObject.Log().Info(CultureInfo.InvariantCulture, "{0} OnCompleted", message)); } - private class TestEnableLogger(LoggingRegistrationScope.TestLogger logger) : IEnableLogger + private class TestEnableLogger(TestLogger logger) : IEnableLogger { public int InfoCount => logger.Messages.Count(m => m.logLevel == LogLevel.Info); diff --git a/src/tests/ReactiveUI.Tests/Mocks/AnotherViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/AnotherViewModel.cs index 10f77086f2..73c2762f80 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/AnotherViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/AnotherViewModel.cs @@ -3,9 +3,9 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A mock view model. +/// A mock view model. /// public class AnotherViewModel : ReactiveObject; diff --git a/src/tests/ReactiveUI.Tests/Mocks/CommandBindViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/CommandBindViewModel.cs index 4933d65625..0d3e28497b 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/CommandBindViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/CommandBindViewModel.cs @@ -3,10 +3,10 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A mock view model. +/// A mock view model. /// public class CommandBindViewModel : ReactiveObject { @@ -16,7 +16,7 @@ public class CommandBindViewModel : ReactiveObject private int _value; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public CommandBindViewModel() { @@ -26,7 +26,7 @@ public CommandBindViewModel() } /// - /// Gets or sets the command1. + /// Gets or sets the command1. /// public ReactiveCommand Command1 { @@ -35,7 +35,7 @@ public ReactiveCommand Command1 } /// - /// Gets or sets the command2. + /// Gets or sets the command2. /// public ReactiveCommand Command2 { @@ -44,12 +44,12 @@ public ReactiveCommand Command2 } /// - /// Gets or sets the nested view model. + /// Gets or sets the nested view model. /// public FakeNestedViewModel NestedViewModel { get; set; } /// - /// Gets or sets the value. + /// Gets or sets the value. /// public int Value { diff --git a/src/tests/ReactiveUI.Tests/Mocks/ExampleViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/ExampleViewModel.cs index d8025bfe09..d77045b030 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/ExampleViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/ExampleViewModel.cs @@ -3,9 +3,9 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A mock view model. +/// A mock view model. /// public class ExampleViewModel : ReactiveObject; diff --git a/src/tests/ReactiveUI.Tests/Mocks/ExampleWindowViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/ExampleWindowViewModel.cs index 11e5f36a8f..c90c018dc9 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/ExampleWindowViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/ExampleWindowViewModel.cs @@ -3,9 +3,9 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A mock view model. +/// A mock view model. /// public class ExampleWindowViewModel : ReactiveObject; diff --git a/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionModel.cs b/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionModel.cs index d80300e6de..ad702b779c 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionModel.cs @@ -3,10 +3,10 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A collection model. +/// A collection model. /// public class FakeCollectionModel : ReactiveObject { @@ -15,10 +15,10 @@ public class FakeCollectionModel : ReactiveObject private int _someNumber; /// - /// Gets or sets a value indicating whether this instance is hidden. + /// Gets or sets a value indicating whether this instance is hidden. /// /// - /// true if this instance is hidden; otherwise, false. + /// true if this instance is hidden; otherwise, false. /// public bool IsHidden { @@ -27,7 +27,7 @@ public bool IsHidden } /// - /// Gets or sets some number. + /// Gets or sets some number. /// public int SomeNumber { diff --git a/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionViewModel.cs index 4cb3fcfe49..3403657043 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionViewModel.cs @@ -3,33 +3,36 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A mock view model. +/// A mock view model. /// public class FakeCollectionViewModel : ReactiveObject { private readonly ObservableAsPropertyHelper _numberAsString; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The model. public FakeCollectionViewModel(FakeCollectionModel model) { Model = model; - this.WhenAny(static x => x.Model.SomeNumber, static x => x.Value.ToString()).ToProperty(this, static x => x.NumberAsString, out _numberAsString); + this.WhenAny(static x => x.Model.SomeNumber, static x => x.Value.ToString()).ToProperty( + this, + static x => x.NumberAsString, + out _numberAsString); } /// - /// Gets or sets the model. + /// Gets the number as string. /// - public FakeCollectionModel Model { get; protected set; } + public string? NumberAsString => _numberAsString.Value; /// - /// Gets the number as string. + /// Gets or sets the model. /// - public string? NumberAsString => _numberAsString.Value; + public FakeCollectionModel Model { get; protected set; } } diff --git a/src/tests/ReactiveUI.Tests/Mocks/FakeNestedViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/FakeNestedViewModel.cs index 26e85b2620..ef2710a731 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/FakeNestedViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/FakeNestedViewModel.cs @@ -3,20 +3,20 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A fake nested view model. +/// A fake nested view model. /// public class FakeNestedViewModel : ReactiveObject { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public FakeNestedViewModel() => NestedCommand = ReactiveCommand.Create(static () => { }); /// - /// Gets or sets the nested command. + /// Gets or sets the nested command. /// public ReactiveCommand NestedCommand { get; protected set; } } diff --git a/src/tests/ReactiveUI.Tests/Mocks/FakeViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/FakeViewModel.cs index 27ac3205ac..bebc0a776e 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/FakeViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/FakeViewModel.cs @@ -3,20 +3,20 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// Fake view model. +/// Fake view model. /// public class FakeViewModel : ReactiveObject { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public FakeViewModel() => Cmd = ReactiveCommand.Create(static () => { }); /// - /// Gets or sets the command. + /// Gets or sets the command. /// public ReactiveCommand Cmd { get; protected set; } } diff --git a/src/tests/ReactiveUI.Tests/Mocks/Foo.cs b/src/tests/ReactiveUI.Tests/Mocks/Foo.cs index d35557667a..c9871409e3 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/Foo.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/Foo.cs @@ -13,7 +13,7 @@ public class Foo public async Task SetValueAsync(int value) { - await RxApp.TaskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(10)); + await RxSchedulers.TaskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(10)); Value = value; return Unit.Default; } diff --git a/src/tests/ReactiveUI.Tests/Mocks/FooViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/FooViewModel.cs index ffc8de6214..9134ab83a5 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/FooViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/FooViewModel.cs @@ -19,7 +19,11 @@ public FooViewModel(Foo foo) .Subscribe(); } - public int Setpoint { get => _setpoint; set => this.RaiseAndSetIfChanged(ref _setpoint, value); } - public Foo Foo { get; } + + public int Setpoint + { + get => _setpoint; + set => this.RaiseAndSetIfChanged(ref _setpoint, value); + } } diff --git a/src/tests/ReactiveUI.Tests/Mocks/InteractionAncestorView.cs b/src/tests/ReactiveUI.Tests/Mocks/InteractionAncestorView.cs index 6893b29769..bb57934f81 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/InteractionAncestorView.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/InteractionAncestorView.cs @@ -3,26 +3,26 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A ancestor view. +/// A ancestor view. /// public class InteractionAncestorView : ReactiveObject, IViewFor { private InteractionAncestorViewModel? _viewModel; - /// - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (InteractionAncestorViewModel?)value; - } - - /// + /// public InteractionAncestorViewModel? ViewModel { get => _viewModel; set => this.RaiseAndSetIfChanged(ref _viewModel, value); } + + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (InteractionAncestorViewModel?)value; + } } diff --git a/src/tests/ReactiveUI.Tests/Mocks/InteractionAncestorViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/InteractionAncestorViewModel.cs index 0ab33df191..1153bb1aad 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/InteractionAncestorViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/InteractionAncestorViewModel.cs @@ -3,10 +3,10 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A ancestor view model. +/// A ancestor view model. /// /// public class InteractionAncestorViewModel : ReactiveObject @@ -14,15 +14,12 @@ public class InteractionAncestorViewModel : ReactiveObject private InteractionBindViewModel _interactionBindViewModel; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public InteractionAncestorViewModel() - { - _interactionBindViewModel = new InteractionBindViewModel(); - } + public InteractionAncestorViewModel() => _interactionBindViewModel = new InteractionBindViewModel(); /// - /// Gets or sets the interaction view model. + /// Gets or sets the interaction view model. /// public InteractionBindViewModel InteractionViewModel { diff --git a/src/tests/ReactiveUI.Tests/Mocks/InteractionBindView.cs b/src/tests/ReactiveUI.Tests/Mocks/InteractionBindView.cs index de4046e58a..3854b11a7e 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/InteractionBindView.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/InteractionBindView.cs @@ -3,26 +3,26 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A bind view. +/// A bind view. /// public class InteractionBindView : ReactiveObject, IViewFor { private InteractionBindViewModel? _viewModel; - /// - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (InteractionBindViewModel?)value; - } - - /// + /// public InteractionBindViewModel? ViewModel { get => _viewModel; set => this.RaiseAndSetIfChanged(ref _viewModel, value); } + + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (InteractionBindViewModel?)value; + } } diff --git a/src/tests/ReactiveUI.Tests/Mocks/InteractionBindViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/InteractionBindViewModel.cs index 63163f6b15..fd7bc50788 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/InteractionBindViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/InteractionBindViewModel.cs @@ -3,10 +3,10 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A bind view model. +/// A bind view model. /// /// public class InteractionBindViewModel : ReactiveObject @@ -14,15 +14,12 @@ public class InteractionBindViewModel : ReactiveObject private Interaction _interaction1; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public InteractionBindViewModel() - { - _interaction1 = new Interaction(); - } + public InteractionBindViewModel() => _interaction1 = new Interaction(); /// - /// Gets or sets the interaction1. + /// Gets or sets the interaction1. /// public Interaction Interaction1 { diff --git a/src/tests/ReactiveUI.Tests/Mocks/NeverUsedViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/NeverUsedViewModel.cs index 486e0a0561..b36193d1de 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/NeverUsedViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/NeverUsedViewModel.cs @@ -3,9 +3,9 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A view model that is never used. +/// A view model that is never used. /// public class NeverUsedViewModel : ReactiveObject; diff --git a/src/tests/ReactiveUI.Tests/Mocks/PropertyBindModel.cs b/src/tests/ReactiveUI.Tests/Mocks/PropertyBindModel.cs index 69cad3e777..46cb31eecd 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/PropertyBindModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/PropertyBindModel.cs @@ -3,20 +3,20 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A property bind model. +/// A property bind model. /// public class PropertyBindModel { /// - /// Gets or sets a thing. + /// Gets or sets another thing. /// - public int AThing { get; set; } + public string? AnotherThing { get; set; } /// - /// Gets or sets another thing. + /// Gets or sets a thing. /// - public string? AnotherThing { get; set; } + public int AThing { get; set; } } diff --git a/src/tests/ReactiveUI.Tests/Mocks/SingleInstanceExampleViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/SingleInstanceExampleViewModel.cs index aa6f423603..148a6867c9 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/SingleInstanceExampleViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/SingleInstanceExampleViewModel.cs @@ -3,9 +3,9 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A single instance example view model. +/// A single instance example view model. /// public class SingleInstanceExampleViewModel : ReactiveObject; diff --git a/src/tests/ReactiveUI.Tests/Mocks/ViewModelWithWeirdName.cs b/src/tests/ReactiveUI.Tests/Mocks/ViewModelWithWeirdName.cs index a4c8d8d555..58c39ac6d7 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/ViewModelWithWeirdName.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/ViewModelWithWeirdName.cs @@ -3,9 +3,9 @@ // 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; +namespace ReactiveUI.Tests.Mocks; /// -/// A view model with a weird name. +/// A view model with a weird name. /// public class ViewModelWithWeirdName : ReactiveObject; diff --git a/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/Mocks/OAPHIndexerTestFixture.cs b/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/Mocks/OAPHIndexerTestFixture.cs index afc55e365f..04d96f4961 100644 --- a/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/Mocks/OAPHIndexerTestFixture.cs +++ b/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/Mocks/OAPHIndexerTestFixture.cs @@ -3,17 +3,17 @@ // 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; +namespace ReactiveUI.Tests.ObservableAsPropertyHelper.Mocks; /// -/// A test fixture for OAPH. +/// A test fixture for OAPH. /// internal class OAPHIndexerTestFixture : ReactiveObject { private string? _text; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public OAPHIndexerTestFixture(int test, IScheduler scheduler) { @@ -21,26 +21,26 @@ public OAPHIndexerTestFixture(int test, IScheduler scheduler) { case 0: var temp = this.WhenAnyValue(static f => f.Text) - .ToProperty(this, static f => f["Whatever"], scheduler: scheduler) - .Value; + .ToProperty(this, static f => f["Whatever"], scheduler: scheduler) + .Value; break; case 1: var temp1 = this.WhenAnyValue(static f => f.Text) - .ToProperty(new ReactiveObject(), static f => f.ToString(), scheduler: scheduler) - .Value; + .ToProperty(new ReactiveObject(), static f => f.ToString(), scheduler: scheduler) + .Value; break; case 2: var temp2 = Observable.Return("happy") - .ToProperty(this, string.Empty, scheduler: scheduler) - .Value; + .ToProperty(this, string.Empty, scheduler: scheduler) + .Value; break; } } /// - /// Gets or sets the text. + /// Gets or sets the text. /// public string? Text { @@ -49,7 +49,7 @@ public string? Text } /// - /// Gets the string with the specified property name. + /// Gets the string with the specified property name. /// /// Name of the property. /// The string. diff --git a/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs b/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs index f7eb672dfa..53004595a8 100644 --- a/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs +++ b/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs @@ -4,151 +4,66 @@ // See the LICENSE file in the project root for full license information. using DynamicData; +using ReactiveUI.Tests.ObservableAsPropertyHelper.Mocks; +using ReactiveUI.Tests.ReactiveObjects.Mocks; +using ReactiveUI.Tests.Utilities; -using Microsoft.Reactive.Testing; - -using ReactiveUI.Testing; - -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.ObservableAsPropertyHelper; public class ObservableAsPropertyHelperTest { /// - /// Tests that Observable As Property Helpers should fire change notifications. + /// No thrown-exceptions subscriber equals OAPH death. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OaphShouldFireChangeNotifications() + [TestExecutor] + public async Task NoThrownExceptionsSubscriberEqualsOaphDeath() { - var input = new[] { 1, 2, 3, 3, 4 }.ToObservable(); - var output = new List(); - - await new TestScheduler().With(async scheduler => - { - var fixture = new ObservableAsPropertyHelper( - input, - x => output.Add(x), - -5, - scheduler: scheduler); + var scheduler = TestContext.Current!.GetScheduler(); + var input = new Subject(); + var fixture = new ObservableAsPropertyHelper(input, _ => { }, -5, scheduler: ImmediateScheduler.Instance); - scheduler.Start(); + await Assert.That(fixture.Value).IsEqualTo(-5); + new[] { 1, 2, 3, 4 }.Run(x => input.OnNext(x)); - using (Assert.Multiple()) - { - await Assert.That(fixture.Value).IsEqualTo(input.LastAsync().Wait()); + var exception = Assert.Throws(() => input.OnError(new Exception("Die!"))); - // Suppresses duplicate notifications (note single '3') - await Assert.That(output).IsEquivalentTo([-5, 1, 2, 3, 4]); - } - }); - } - - /// - /// Tests that Observable As Property Helpers should skip first value if it matches the initial value. - /// - /// A representing the asynchronous operation. - [Test] - public async Task OaphShouldSkipFirstValueIfItMatchesTheInitialValue() - { - var input = new[] { 1, 2, 3 }.ToObservable(); - var output = new List(); - - await new TestScheduler().With(async scheduler => - { - var fixture = new ObservableAsPropertyHelper( - input, - x => output.Add(x), - 1, - scheduler: scheduler); - - scheduler.Start(); - - using (Assert.Multiple()) - { - await Assert.That(fixture.Value).IsEqualTo(input.LastAsync().Wait()); - await Assert.That(output).IsEquivalentTo([1, 2, 3]); - } - }); - } - - /// - /// Tests that OAPH should provide initial value immediately regardless of scheduler. - /// - /// A representing the asynchronous operation. - [Test] - public async Task OaphShouldProvideInitialValueImmediatelyRegardlessOfScheduler() - { - var output = new List(); - - await new TestScheduler().With(async scheduler => - { - var fixture = new ObservableAsPropertyHelper( - Observable.Never, - x => output.Add(x), - 32, - scheduler: scheduler); - - await Assert.That(fixture.Value).IsEqualTo(32); - }); - } - - /// - /// Tests that OAPH should provide latest value. - /// - /// A representing the asynchronous operation. - [Test] - public async Task OaphShouldProvideLatestValue() => - await new TestScheduler().With(async scheduler => + using (Assert.Multiple()) { - var input = new Subject(); - - var fixture = new ObservableAsPropertyHelper( - input, - _ => { }, - -5, - scheduler: scheduler); - - await Assert.That(fixture.Value).IsEqualTo(-5); - - new[] { 1, 2, 3, 4 }.Run(x => input.OnNext(x)); - scheduler.Start(); - await Assert.That(fixture.Value).IsEqualTo(4); - - input.OnCompleted(); - scheduler.Start(); + await Assert.That(exception.InnerException?.Message).IsEqualTo("Die!"); await Assert.That(fixture.Value).IsEqualTo(4); - }); + } + } /// - /// OAPH should subscribe immediately to source. + /// Nullable types test shouldn't need decorators with ToProperty. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OaphShouldSubscribeImmediatelyToSource() + public async Task NullableTypesTestShouldNotNeedDecorators2_ToProperty() { - var isSubscribed = false; - - var observable = Observable.Create(o => - { - isSubscribed = true; - o.OnNext(42); - o.OnCompleted(); - return Disposable.Empty; - }); + var fixture = new WhenAnyTestFixture(); + fixture.WhenAnyValue( + static x => x.ProjectService.ProjectsNullable, + static x => x.AccountService.AccountUsersNullable) + .Where(static tuple => tuple.Item1.Count > 0 && tuple.Item2.Count > 0) + .Select(static tuple => + { + var (_, users) = tuple; + return users.Values.Count(static x => !string.IsNullOrWhiteSpace(x?.LastName)); + }) + .ToProperty(fixture, static x => x.AccountsFound, out var helper); - var fixture = new ObservableAsPropertyHelper(observable, _ => { }, 0); + fixture.AccountsFoundHelper = helper; - using (Assert.Multiple()) - { - await Assert.That(isSubscribed).IsTrue(); - await Assert.That(fixture.Value).IsEqualTo(42); - } + await Assert.That(fixture.AccountsFound).IsEqualTo(3); } /// - /// Defer subscription parameter defers subscription to source. + /// Defer subscription parameter defers subscription to source. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task OaphDeferSubscriptionParameterDefersSubscriptionToSource() { @@ -174,9 +89,9 @@ public async Task OaphDeferSubscriptionParameterDefersSubscriptionToSource() } /// - /// Defer subscription: IsSubscribed is not true initially. + /// Defer subscription: IsSubscribed is not true initially. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task OaphDeferSubscriptionParameterIsSubscribedIsNotTrueInitially() { @@ -198,9 +113,9 @@ public async Task OaphDeferSubscriptionParameterIsSubscribedIsNotTrueInitially() } /// - /// Defer subscription should not throw if disposed. + /// Defer subscription should not throw if disposed. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task OaphDeferSubscriptionShouldNotThrowIfDisposed() { @@ -227,212 +142,222 @@ await Assert.That(() => } /// - /// Verifies that deferring subscription with an initial value does not emit the initial value. + /// Verifies that deferred subscription with an initial value provided by a function emits the initial value + /// only when subscribed and confirms the function is accessed at that point. + /// Ensures that the subscription state and access status align with the expected behavior. /// - /// The initial value to test with. - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - [Arguments(0)] - [Arguments(42)] - public async Task OaphDeferSubscriptionWithInitialValueShouldNotEmitInitialValue(int initialValue) + public async Task OaphDeferSubscriptionWithInitialFuncValueEmitInitialValueWhenSubscribed() { var observable = Observable.Empty(); - var fixture = new ObservableAsPropertyHelper(observable, _ => { }, initialValue, deferSubscription: true); - - await Assert.That(fixture.IsSubscribed).IsFalse(); + var wasAccessed = false; - int? emittedValue = null; - fixture.Source.Subscribe(val => emittedValue = val); + var fixture = new ObservableAsPropertyHelper( + observable, + _ => { }, + getInitialValue: GetInitialValue, + deferSubscription: true); using (Assert.Multiple()) { - await Assert.That(emittedValue).IsNull(); await Assert.That(fixture.IsSubscribed).IsFalse(); + await Assert.That(wasAccessed).IsFalse(); + } + + var result = fixture.Value; + + using (Assert.Multiple()) + { + await Assert.That(fixture.IsSubscribed).IsTrue(); + await Assert.That(wasAccessed).IsTrue(); + await Assert.That(result).IsEqualTo(42); + } + + return; + + int GetInitialValue() + { + wasAccessed = true; + return 42; } } /// - /// Defer subscription with initial function value should not emit initial value nor access function. + /// Ensures that defer subscription with an initial function value does not trigger the OnChanged callback + /// when the source observable provides the same initial value. /// - /// A representing the asynchronous operation. + /// The initial value provided to the ObservableAsPropertyHelper. + /// A representing the asynchronous operation. [Test] - public async Task OaphDeferSubscriptionWithInitialFuncValueShouldNotEmitInitialValueNorAccessFunc() + [Arguments(0)] + [Arguments(42)] + public async Task OAPHDeferSubscriptionWithInitialFuncValueNotCallOnChangedWhenSourceProvidesInitialValue( + int initialValue) { - var observable = Observable.Empty(); + var observable = new Subject(); + var wasOnChangingCalled = false; + var wasOnChangedCalled = false; - var fixture = new ObservableAsPropertyHelper( - observable, _ => { }, getInitialValue: ThrowIfAccessed, deferSubscription: true); + var fixture = new ObservableAsPropertyHelper(observable, OnChanged, OnChanging, () => initialValue, true); - await Assert.That(fixture.IsSubscribed).IsFalse(); + var result = fixture.Value; + await Assert.That(result).IsEqualTo(initialValue); - int? emittedValue = null; - fixture.Source.Subscribe(val => emittedValue = val); + observable.OnNext(initialValue); using (Assert.Multiple()) { - await Assert.That(emittedValue).IsNull(); - await Assert.That(fixture.IsSubscribed).IsFalse(); + await Assert.That(wasOnChangingCalled).IsFalse(); + await Assert.That(wasOnChangedCalled).IsFalse(); } return; - static int ThrowIfAccessed() => throw new Exception(); + void OnChanged(int unused) => wasOnChangedCalled = true; + + void OnChanging(int unused) => wasOnChangingCalled = true; } /// - /// Ensures that defer subscription with an initial value emits the initial value upon subscription. + /// Verifies that deferring subscription with an initial function value does not trigger OnChanged when subscribed. /// - /// - /// The initial value set before any subscription occurs. - /// - /// A representing the asynchronous operation. + /// The initial value to set before any subscription occurs. + /// A representing the asynchronous operation. [Test] [Arguments(0)] [Arguments(42)] - public async Task OaphDeferSubscriptionWithInitialValueEmitInitialValueWhenSubscribed(int initialValue) + public async Task OAPHDeferSubscriptionWithInitialFuncValueNotCallOnChangedWhenSubscribed(int initialValue) { var observable = Observable.Empty(); - var fixture = new ObservableAsPropertyHelper(observable, static _ => { }, initialValue, deferSubscription: true); - await Assert.That(fixture.IsSubscribed).IsFalse(); + var wasOnChangingCalled = false; + var wasOnChangedCalled = false; + + var fixture = new ObservableAsPropertyHelper(observable, OnChanged, OnChanging, () => initialValue, true); + + using (Assert.Multiple()) + { + await Assert.That(fixture.IsSubscribed).IsFalse(); + await Assert.That(wasOnChangingCalled).IsFalse(); + await Assert.That(wasOnChangedCalled).IsFalse(); + } var result = fixture.Value; using (Assert.Multiple()) { await Assert.That(fixture.IsSubscribed).IsTrue(); + await Assert.That(wasOnChangingCalled).IsFalse(); + await Assert.That(wasOnChangedCalled).IsFalse(); await Assert.That(result).IsEqualTo(initialValue); } + + return; + + void OnChanged(int unused) => wasOnChangedCalled = true; + + void OnChanging(int unused) => wasOnChangingCalled = true; } /// - /// Verifies that deferred subscription with an initial value provided by a function emits the initial value - /// only when subscribed and confirms the function is accessed at that point. - /// Ensures that the subscription state and access status align with the expected behavior. + /// Defer subscription with initial function value should not emit initial value nor access function. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OaphDeferSubscriptionWithInitialFuncValueEmitInitialValueWhenSubscribed() + public async Task OaphDeferSubscriptionWithInitialFuncValueShouldNotEmitInitialValueNorAccessFunc() { var observable = Observable.Empty(); - var wasAccessed = false; var fixture = new ObservableAsPropertyHelper( - observable, _ => { }, getInitialValue: GetInitialValue, deferSubscription: true); + observable, + _ => { }, + getInitialValue: ThrowIfAccessed, + deferSubscription: true); - using (Assert.Multiple()) - { - await Assert.That(fixture.IsSubscribed).IsFalse(); - await Assert.That(wasAccessed).IsFalse(); - } + await Assert.That(fixture.IsSubscribed).IsFalse(); - var result = fixture.Value; + int? emittedValue = null; + fixture.Source.Subscribe(val => emittedValue = val); using (Assert.Multiple()) { - await Assert.That(fixture.IsSubscribed).IsTrue(); - await Assert.That(wasAccessed).IsTrue(); - await Assert.That(result).IsEqualTo(42); + await Assert.That(emittedValue).IsNull(); + await Assert.That(fixture.IsSubscribed).IsFalse(); } return; - int GetInitialValue() - { - wasAccessed = true; - return 42; - } + static int ThrowIfAccessed() => throw new Exception(); } /// - /// Verifies that deferring subscription with an initial function value does not trigger OnChanged when subscribed. + /// Ensures that defer subscription with an initial value emits the initial value upon subscription. /// - /// The initial value to set before any subscription occurs. - /// A representing the asynchronous operation. + /// + /// The initial value set before any subscription occurs. + /// + /// A representing the asynchronous operation. [Test] [Arguments(0)] [Arguments(42)] - public async Task OAPHDeferSubscriptionWithInitialFuncValueNotCallOnChangedWhenSubscribed(int initialValue) + public async Task OaphDeferSubscriptionWithInitialValueEmitInitialValueWhenSubscribed(int initialValue) { var observable = Observable.Empty(); - - var wasOnChangingCalled = false; - var wasOnChangedCalled = false; - var fixture = new ObservableAsPropertyHelper( - observable, OnChanged, OnChanging, () => initialValue, true); + observable, + static _ => { }, + initialValue, + true); - using (Assert.Multiple()) - { - await Assert.That(fixture.IsSubscribed).IsFalse(); - await Assert.That(wasOnChangingCalled).IsFalse(); - await Assert.That(wasOnChangedCalled).IsFalse(); - } + await Assert.That(fixture.IsSubscribed).IsFalse(); var result = fixture.Value; using (Assert.Multiple()) { await Assert.That(fixture.IsSubscribed).IsTrue(); - await Assert.That(wasOnChangingCalled).IsFalse(); - await Assert.That(wasOnChangedCalled).IsFalse(); await Assert.That(result).IsEqualTo(initialValue); } - - return; - - void OnChanged(int unused) => wasOnChangedCalled = true; - - void OnChanging(int unused) => wasOnChangingCalled = true; } /// - /// Ensures that defer subscription with an initial function value does not trigger the OnChanged callback - /// when the source observable provides the same initial value. + /// Verifies that deferring subscription with an initial value does not emit the initial value. /// - /// The initial value provided to the ObservableAsPropertyHelper. - /// A representing the asynchronous operation. + /// The initial value to test with. + /// A representing the asynchronous operation. [Test] [Arguments(0)] [Arguments(42)] - public async Task OAPHDeferSubscriptionWithInitialFuncValueNotCallOnChangedWhenSourceProvidesInitialValue(int initialValue) + public async Task OaphDeferSubscriptionWithInitialValueShouldNotEmitInitialValue(int initialValue) { - var observable = new Subject(); - var wasOnChangingCalled = false; - var wasOnChangedCalled = false; - - var fixture = new ObservableAsPropertyHelper(observable, OnChanged, OnChanging, () => initialValue, true); + var observable = Observable.Empty(); + var fixture = new ObservableAsPropertyHelper(observable, _ => { }, initialValue, true); - var result = fixture.Value; - await Assert.That(result).IsEqualTo(initialValue); + await Assert.That(fixture.IsSubscribed).IsFalse(); - observable.OnNext(initialValue); + int? emittedValue = null; + fixture.Source.Subscribe(val => emittedValue = val); using (Assert.Multiple()) { - await Assert.That(wasOnChangingCalled).IsFalse(); - await Assert.That(wasOnChangedCalled).IsFalse(); + await Assert.That(emittedValue).IsNull(); + await Assert.That(fixture.IsSubscribed).IsFalse(); } - - return; - - void OnChanged(int unused) => wasOnChangedCalled = true; - - void OnChanging(int unused) => wasOnChangingCalled = true; } /// - /// Verifies that the initial value of an Observable As Property Helper is emitted correctly. + /// Verifies that the initial value of an Observable As Property Helper is emitted correctly. /// /// The initial value provided to the Observable As Property Helper. - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] [Arguments(0)] [Arguments(42)] public async Task OaphInitialValueShouldEmitInitialValue(int initialValue) { var observable = Observable.Empty(); - var fixture = new ObservableAsPropertyHelper(observable, _ => { }, initialValue, deferSubscription: false); + var fixture = new ObservableAsPropertyHelper(observable, _ => { }, initialValue); await Assert.That(fixture.IsSubscribed).IsTrue(); @@ -443,114 +368,232 @@ public async Task OaphInitialValueShouldEmitInitialValue(int initialValue) } /// - /// OAPH should rethrow errors via ThrownExceptions. + /// Tests that Observable As Property Helpers should fire change notifications. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OaphShouldRethrowErrors() => - await new TestScheduler().With(async scheduler => - { - var input = new Subject(); - var fixture = new ObservableAsPropertyHelper(input, _ => { }, -5, scheduler: scheduler); - var errors = new List(); + [TestExecutor] + public async Task OaphShouldFireChangeNotifications() + { + var scheduler = TestContext.Current!.GetScheduler(); + var input = new[] { 1, 2, 3, 3, 4 }.ToObservable(); + var output = new List(); - await Assert.That(fixture.Value).IsEqualTo(-5); - new[] { 1, 2, 3, 4 }.Run(x => input.OnNext(x)); + var fixture = new ObservableAsPropertyHelper( + input, + x => output.Add(x), + -5, + scheduler: scheduler); - fixture.ThrownExceptions.Subscribe(errors.Add); + // ImmediateScheduler executes synchronously, no need for scheduler.Start() + using (Assert.Multiple()) + { + await Assert.That(fixture.Value).IsEqualTo(input.LastAsync().Wait()); - scheduler.Start(); - await Assert.That(fixture.Value).IsEqualTo(4); + // Suppresses duplicate notifications (note single '3') + await Assert.That(output).IsEquivalentTo([-5, 1, 2, 3, 4]); + } + } - input.OnError(new Exception("Die!")); - scheduler.Start(); + /// + /// Tests that OAPH should provide initial value immediately regardless of scheduler. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OaphShouldProvideInitialValueImmediatelyRegardlessOfScheduler() + { + var scheduler = TestContext.Current!.GetScheduler(); + var output = new List(); - using (Assert.Multiple()) - { - await Assert.That(fixture.Value).IsEqualTo(4); - await Assert.That(errors).Count().IsEqualTo(1); - } - }); + var fixture = new ObservableAsPropertyHelper( + Observable.Never, + x => output.Add(x), + 32, + scheduler: scheduler); + + await Assert.That(fixture.Value).IsEqualTo(32); + } /// - /// No thrown-exceptions subscriber equals OAPH death. + /// Tests that OAPH should provide latest value. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task NoThrownExceptionsSubscriberEqualsOaphDeath() => - await new TestScheduler().With(async scheduler => - { - var input = new Subject(); - var fixture = new ObservableAsPropertyHelper(input, _ => { }, -5, scheduler: ImmediateScheduler.Instance); + [TestExecutor] + public async Task OaphShouldProvideLatestValue() + { + var scheduler = TestContext.Current!.GetScheduler(); + var input = new Subject(); - await Assert.That(fixture.Value).IsEqualTo(-5); - new[] { 1, 2, 3, 4 }.Run(x => input.OnNext(x)); + var fixture = new ObservableAsPropertyHelper( + input, + _ => { }, + -5, + scheduler: scheduler); - input.OnError(new Exception("Die!")); + await Assert.That(fixture.Value).IsEqualTo(-5); - var failed = true; - try - { - scheduler.Start(); - } - catch (Exception ex) - { - failed = ex.InnerException?.Message != "Die!"; - } + new[] { 1, 2, 3, 4 }.Run(x => input.OnNext(x)); - using (Assert.Multiple()) - { - await Assert.That(failed).IsFalse(); - await Assert.That(fixture.Value).IsEqualTo(4); - } - }); + // ImmediateScheduler executes synchronously, no need for scheduler.Start() + await Assert.That(fixture.Value).IsEqualTo(4); + + input.OnCompleted(); + + // ImmediateScheduler executes synchronously, no need for scheduler.Start() + await Assert.That(fixture.Value).IsEqualTo(4); + } /// - /// ToProperty should fire both Changing and Changed. + /// OAPH should rethrow errors via ThrownExceptions. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToPropertyShouldFireBothChangingAndChanged() + [TestExecutor] + public async Task OaphShouldRethrowErrors() { - var fixture = new OaphTestFixture(); + var scheduler = TestContext.Current!.GetScheduler(); + var input = new Subject(); + var fixture = new ObservableAsPropertyHelper(input, _ => { }, -5, scheduler: scheduler); + var errors = new List(); - // NB: Hack to connect up the OAPH - _ = (fixture.FirstThreeLettersOfOneWord ?? string.Empty).Substring(0, 0); + await Assert.That(fixture.Value).IsEqualTo(-5); + new[] { 1, 2, 3, 4 }.Run(x => input.OnNext(x)); + + fixture.ThrownExceptions.Subscribe(errors.Add); - fixture.ObservableForProperty(static x => x.FirstThreeLettersOfOneWord, beforeChange: true) - .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var resultChanging).Subscribe(); - fixture.ObservableForProperty(static x => x.FirstThreeLettersOfOneWord, beforeChange: false) - .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var resultChanged).Subscribe(); + // ImmediateScheduler executes synchronously, no need for scheduler.Start() + await Assert.That(fixture.Value).IsEqualTo(4); + input.OnError(new Exception("Die!")); + + // ImmediateScheduler executes synchronously, no need for scheduler.Start() using (Assert.Multiple()) { - await Assert.That(resultChanging).IsEmpty(); - await Assert.That(resultChanged).IsEmpty(); + await Assert.That(fixture.Value).IsEqualTo(4); + await Assert.That(errors).Count().IsEqualTo(1); } + } - fixture.IsOnlyOneWord = "FooBar"; + /// + /// Tests that Observable As Property Helpers should skip first value if it matches the initial value. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OaphShouldSkipFirstValueIfItMatchesTheInitialValue() + { + var scheduler = TestContext.Current!.GetScheduler(); + var input = new[] { 1, 2, 3 }.ToObservable(); + var output = new List(); + + var fixture = new ObservableAsPropertyHelper( + input, + x => output.Add(x), + 1, + scheduler: scheduler); + + // ImmediateScheduler executes synchronously, no need for scheduler.Start() using (Assert.Multiple()) { - await Assert.That(resultChanging).Count().IsEqualTo(1); - await Assert.That(resultChanged).Count().IsEqualTo(1); - await Assert.That(resultChanging[0].Value).IsEqualTo(string.Empty); - await Assert.That(resultChanged[0].Value).IsEqualTo("Foo"); + await Assert.That(fixture.Value).IsEqualTo(input.LastAsync().Wait()); + await Assert.That(output).IsEquivalentTo([1, 2, 3]); } + } + + /// + /// OAPH should subscribe immediately to source. + /// + /// A representing the asynchronous operation. + [Test] + public async Task OaphShouldSubscribeImmediatelyToSource() + { + var isSubscribed = false; + + var observable = Observable.Create(o => + { + isSubscribed = true; + o.OnNext(42); + o.OnCompleted(); + return Disposable.Empty; + }); + + var fixture = new ObservableAsPropertyHelper(observable, _ => { }, 0); - fixture.IsOnlyOneWord = "Bazz"; using (Assert.Multiple()) { - await Assert.That(resultChanging).Count().IsEqualTo(2); - await Assert.That(resultChanged).Count().IsEqualTo(2); - await Assert.That(resultChanging[1].Value).IsEqualTo("Foo"); - await Assert.That(resultChanged[1].Value).IsEqualTo("Baz"); + await Assert.That(isSubscribed).IsTrue(); + await Assert.That(fixture.Value).IsEqualTo(42); } } /// - /// ToProperty(nameof) should raise standard notifications. + /// ToProperty with indexer notifies expected property name. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. + [Test] + public async Task ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName() + { + var fixture = new OAPHIndexerTestFixture(0, ImmediateScheduler.Instance); + var propertiesChanged = new List(); + + fixture.PropertyChanged += (_, args) => + { + if (args.PropertyName is not null) + { + propertiesChanged.Add(args.PropertyName); + } + }; + + fixture.Text = "awesome"; + + await Assert.That(propertiesChanged).IsEquivalentTo(["Text", "Item[]"]); + } + + /// + /// Indexer: not supported path throws NotSupportedException. + /// + [Test] + [TestExecutor] + public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName1() + { + var scheduler = TestContext.Current!.GetScheduler(); + var propertiesChanged = new List(); + + Assert.Throws(() => + { + var fixture = new OAPHIndexerTestFixture( + 1, + scheduler); + + fixture.PropertyChanged += (_, args) => + { + if (args.PropertyName is not null) + { + propertiesChanged.Add(args.PropertyName); + } + }; + + fixture.Text = "awesome"; + }); + } + + /// + /// Indexer: invalid path throws ArgumentException. + /// + [Test] + [TestExecutor] + public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName2() + { + var scheduler = TestContext.Current!.GetScheduler(); + Assert.Throws(() => _ = new OAPHIndexerTestFixture(2, scheduler)); + } + + /// + /// ToProperty(nameof) should raise standard notifications. + /// + /// A representing the asynchronous operation. [Test] public async Task ToProperty_NameOf_ShouldFireBothChangingAndChanged() { @@ -578,19 +621,28 @@ public async Task ToProperty_NameOf_ShouldFireBothChangingAndChanged() } /// - /// Ensures that the ToProperty method, when used with nameof, produces valid output values - /// for derived properties by comparing test data against expected values. + /// Ensures that the ToProperty method, when used with nameof, produces valid output values + /// for derived properties by comparing test data against expected values. /// /// An array of input strings to evaluate. - /// An array of expected first three letters for each input string in . - /// An array of expected last three letters for each input string in . - /// A representing the asynchronous operation. + /// + /// An array of expected first three letters for each input string in + /// . + /// + /// + /// An array of expected last three letters for each input string in + /// . + /// + /// A representing the asynchronous operation. [Test] [Arguments( - new[] { "FooBar", "Bazz" }, - new[] { "Foo", "Baz" }, - new[] { "Bar", "azz" })] - public async Task ToProperty_NameOf_ValidValuesProduced(string[] testWords, string[] first3Letters, string[] last3Letters) + new[] { "FooBar", "Bazz" }, + new[] { "Foo", "Baz" }, + new[] { "Bar", "azz" })] + public async Task ToProperty_NameOf_ValidValuesProduced( + string[] testWords, + string[] first3Letters, + string[] last3Letters) { if (testWords is null) { @@ -609,16 +661,16 @@ public async Task ToProperty_NameOf_ValidValuesProduced(string[] testWords, stri var fixture = new OaphNameOfTestFixture(); - fixture.ObservableForProperty(x => x.FirstThreeLettersOfOneWord, beforeChange: true) - .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var firstThreeChanging).Subscribe(); - fixture.ObservableForProperty(x => x.LastThreeLettersOfOneWord, beforeChange: true) - .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var lastThreeChanging).Subscribe(); + fixture.ObservableForProperty(x => x.FirstThreeLettersOfOneWord, true) + .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var firstThreeChanging).Subscribe(); + fixture.ObservableForProperty(x => x.LastThreeLettersOfOneWord, true) + .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var lastThreeChanging).Subscribe(); var changing = new[] { firstThreeChanging!, lastThreeChanging }; - fixture.ObservableForProperty(x => x.FirstThreeLettersOfOneWord, beforeChange: false) - .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var firstThreeChanged).Subscribe(); - fixture.ObservableForProperty(x => x.LastThreeLettersOfOneWord, beforeChange: false) - .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var lastThreeChanged).Subscribe(); + fixture.ObservableForProperty(x => x.FirstThreeLettersOfOneWord, false) + .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var firstThreeChanged).Subscribe(); + fixture.ObservableForProperty(x => x.LastThreeLettersOfOneWord, false) + .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var lastThreeChanged).Subscribe(); var changed = new[] { firstThreeChanged!, lastThreeChanged }; using (Assert.Multiple()) @@ -651,84 +703,44 @@ public async Task ToProperty_NameOf_ValidValuesProduced(string[] testWords, stri } /// - /// ToProperty with indexer notifies expected property name. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName() - { - var fixture = new OAPHIndexerTestFixture(0, ImmediateScheduler.Instance); - var propertiesChanged = new List(); - - fixture.PropertyChanged += (_, args) => - { - if (args.PropertyName is not null) - { - propertiesChanged.Add(args.PropertyName); - } - }; - - fixture.Text = "awesome"; - - await Assert.That(propertiesChanged).IsEquivalentTo(["Text", "Item[]"]); - } - - /// - /// Indexer: not supported path throws NotSupportedException. + /// ToProperty should fire both Changing and Changed. /// + /// A representing the asynchronous operation. [Test] - public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName1() + public async Task ToPropertyShouldFireBothChangingAndChanged() { - var propertiesChanged = new List(); + var fixture = new OaphTestFixture(); - new TestScheduler().With(scheduler => - Assert.Throws(() => - { - var fixture = new OAPHIndexerTestFixture( - 1, - scheduler); - - fixture.PropertyChanged += (_, args) => - { - if (args.PropertyName is not null) - { - propertiesChanged.Add(args.PropertyName); - } - }; - - fixture.Text = "awesome"; - })); - } + // NB: Hack to connect up the OAPH + _ = (fixture.FirstThreeLettersOfOneWord ?? string.Empty).Substring(0, 0); - /// - /// Indexer: invalid path throws ArgumentException. - /// - [Test] - public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName2() => - new TestScheduler().With(scheduler => - Assert.Throws(() => _ = new OAPHIndexerTestFixture(2, scheduler))); + fixture.ObservableForProperty(static x => x.FirstThreeLettersOfOneWord, true) + .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var resultChanging).Subscribe(); + fixture.ObservableForProperty(static x => x.FirstThreeLettersOfOneWord, false) + .ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var resultChanged).Subscribe(); - /// - /// Nullable types test shouldn't need decorators with ToProperty. - /// - /// A representing the asynchronous operation. - [Test] - public async Task NullableTypesTestShouldNotNeedDecorators2_ToProperty() - { - var fixture = new WhenAnyTestFixture(); - fixture.WhenAnyValue( - static x => x.ProjectService.ProjectsNullable, - static x => x.AccountService.AccountUsersNullable) - .Where(static tuple => tuple.Item1.Count > 0 && tuple.Item2.Count > 0) - .Select(static tuple => - { - var (_, users) = tuple; - return users.Values.Count(static x => !string.IsNullOrWhiteSpace(x?.LastName)); - }) - .ToProperty(fixture, static x => x.AccountsFound, out var helper); + using (Assert.Multiple()) + { + await Assert.That(resultChanging).IsEmpty(); + await Assert.That(resultChanged).IsEmpty(); + } - fixture.AccountsFoundHelper = helper; + fixture.IsOnlyOneWord = "FooBar"; + using (Assert.Multiple()) + { + await Assert.That(resultChanging).Count().IsEqualTo(1); + await Assert.That(resultChanged).Count().IsEqualTo(1); + await Assert.That(resultChanging[0].Value).IsEqualTo(string.Empty); + await Assert.That(resultChanged[0].Value).IsEqualTo("Foo"); + } - await Assert.That(fixture.AccountsFound).IsEqualTo(3); + fixture.IsOnlyOneWord = "Bazz"; + using (Assert.Multiple()) + { + await Assert.That(resultChanging).Count().IsEqualTo(2); + await Assert.That(resultChanged).Count().IsEqualTo(2); + await Assert.That(resultChanging[1].Value).IsEqualTo("Foo"); + await Assert.That(resultChanged[1].Value).IsEqualTo("Baz"); + } } } diff --git a/src/tests/ReactiveUI.Tests/ObservableForProperty/OAPHCreationHelperMixinTest.cs b/src/tests/ReactiveUI.Tests/ObservableForProperty/OAPHCreationHelperMixinTest.cs index a2e1ba8a01..64c137f91a 100644 --- a/src/tests/ReactiveUI.Tests/ObservableForProperty/OAPHCreationHelperMixinTest.cs +++ b/src/tests/ReactiveUI.Tests/ObservableForProperty/OAPHCreationHelperMixinTest.cs @@ -3,152 +3,195 @@ // 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.Linq.Expressions; -using System.Reactive.Concurrency; - -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.ObservableForProperty; /// -/// Tests for . +/// Tests for . /// public class OAPHCreationHelperMixinTest { /// - /// Tests that ToProperty with Expression throws ArgumentNullException when property is null. + /// Tests that internal ObservableToProperty with Expression extracts correct property name. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithExpression_ThrowsOnNullProperty() + public async Task ObservableToProperty_WithExpression_ExtractsCorrectPropertyName() { var source = new TestReactiveObject(); - var observable = Observable.Return("test"); + var observable = Observable.Return("test").ObserveOn(ImmediateScheduler.Instance); + string? capturedPropertyName = null; - await Assert.That(() => observable.ToProperty(source, (Expression>)null!)) - .Throws(); + source.PropertyChanged += (s, e) => capturedPropertyName = e.PropertyName; + + var result = source.ObservableToProperty( + observable, + x => x.TestProperty, + scheduler: ImmediateScheduler.Instance); + + await Assert.That(capturedPropertyName).IsEqualTo(nameof(source.TestProperty)); + + result.Dispose(); } /// - /// Tests that ToProperty with Expression and initial value throws ArgumentNullException when property is null. + /// Tests that internal ObservableToProperty with Expression and getInitialValue creates helper and raises + /// notifications. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithExpressionAndInitialValue_ThrowsOnNullProperty() + public async Task ObservableToProperty_WithExpressionAndGetInitialValue_CreatesHelperAndRaisesNotifications() { var source = new TestReactiveObject(); - var observable = Observable.Return("test"); + var observable = Observable.Return("newValue").ObserveOn(ImmediateScheduler.Instance); + var changingFired = false; + var changedFired = false; - await Assert.That(() => observable.ToProperty(source, (Expression>)null!, "initial")) - .Throws(); - } + source.PropertyChanging += (s, e) => + { + if (e.PropertyName == nameof(source.TestProperty)) + { + changingFired = true; + } + }; - /// - /// Tests that ToProperty with string name throws ArgumentNullException when target is null. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ToProperty_WithStringName_ThrowsOnNullTarget() - { - var observable = Observable.Return("test"); + source.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(source.TestProperty)) + { + changedFired = true; + } + }; - await Assert.That(() => observable.ToProperty(null!, "TestProperty")) - .Throws(); - } + var result = source.ObservableToProperty( + observable, + x => x.TestProperty, + () => "initial", + scheduler: ImmediateScheduler.Instance); - /// - /// Tests that ToProperty with string name throws ArgumentNullException when observable is null. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ToProperty_WithStringName_ThrowsOnNullObservable() - { - var source = new TestReactiveObject(); + await Assert.That(result).IsNotNull(); + await Assert.That(result.Value).IsEqualTo("newValue"); + await Assert.That(changingFired).IsTrue(); + await Assert.That(changedFired).IsTrue(); - await Assert.That(() => ((IObservable)null!).ToProperty(source, "TestProperty")) - .Throws(); + result.Dispose(); } /// - /// Tests that ToProperty with string name throws ArgumentException when property name is null. + /// Tests that internal ObservableToProperty with Expression without initialValue uses default. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithStringName_ThrowsOnNullPropertyName() + public async Task ObservableToProperty_WithExpressionNoInitialValue_UsesDefault() { var source = new TestReactiveObject(); - var observable = Observable.Return("test"); + var observable = Observable.Never(); - await Assert.That(() => observable.ToProperty(source, (string)null!)) - .Throws(); - } + var result = source.ObservableToProperty( + observable, + x => x.TestProperty, + scheduler: ImmediateScheduler.Instance); - /// - /// Tests that ToProperty with string name throws ArgumentException when property name is empty. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ToProperty_WithStringName_ThrowsOnEmptyPropertyName() - { - var source = new TestReactiveObject(); - var observable = Observable.Return("test"); + await Assert.That(result).IsNotNull(); + await Assert.That(result.Value).IsNull(); - await Assert.That(() => observable.ToProperty(source, string.Empty)) - .Throws(); + result.Dispose(); } /// - /// Tests that ToProperty with string name throws ArgumentException when property name is whitespace. + /// Tests that internal ObservableToProperty with string name and getInitialValue creates helper. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithStringName_ThrowsOnWhitespacePropertyName() + public async Task ObservableToProperty_WithStringNameAndGetInitialValue_CreatesHelper() { var source = new TestReactiveObject(); - var observable = Observable.Return("test"); + var observable = Observable.Return("newValue").ObserveOn(ImmediateScheduler.Instance); + var changingFired = false; + var changedFired = false; - await Assert.That(() => observable.ToProperty(source, " ")) - .Throws(); + source.PropertyChanging += (s, e) => + { + if (e.PropertyName == nameof(source.TestProperty)) + { + changingFired = true; + } + }; + + source.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(source.TestProperty)) + { + changedFired = true; + } + }; + + var result = source.ObservableToProperty( + observable, + nameof(source.TestProperty), + () => "initial", + scheduler: ImmediateScheduler.Instance); + + await Assert.That(result).IsNotNull(); + await Assert.That(result.Value).IsEqualTo("newValue"); + await Assert.That(changingFired).IsTrue(); + await Assert.That(changedFired).IsTrue(); + + result.Dispose(); } /// - /// Tests that ToProperty with string name and out parameter returns the helper through out parameter. + /// Tests that internal ObservableToProperty with string name without initialValue uses default. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithStringNameAndOut_ReturnsHelperThroughOutParameter() + public async Task ObservableToProperty_WithStringNameNoInitialValue_UsesDefault() { var source = new TestReactiveObject(); - var observable = Observable.Return("test"); + var observable = Observable.Never(); - var result = observable.ToProperty(source, nameof(source.TestProperty), out var outResult); + var result = source.ObservableToProperty( + observable, + nameof(source.TestProperty), + scheduler: ImmediateScheduler.Instance); await Assert.That(result).IsNotNull(); - await Assert.That(outResult).IsEqualTo(result); + await Assert.That(result.Value).IsNull(); result.Dispose(); } /// - /// Tests that ToProperty with Expression and out parameter returns the helper through out parameter. + /// Tests that ToProperty with deferSubscription defers subscription until Value is accessed. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithExpressionAndOut_ReturnsHelperThroughOutParameter() + public async Task ToProperty_WithDeferSubscription_DefersSubscriptionUntilValueAccessed() { var source = new TestReactiveObject(); - var observable = Observable.Return("test"); + var subscribed = false; + var observable = Observable.Create(observer => + { + subscribed = true; + observer.OnNext("test"); + observer.OnCompleted(); + return Disposable.Empty; + }); - var result = observable.ToProperty(source, x => x.TestProperty, out var outResult); + var result = observable.ToProperty(source, x => x.TestProperty, true, ImmediateScheduler.Instance); - await Assert.That(result).IsNotNull(); - await Assert.That(outResult).IsEqualTo(result); + await Assert.That(subscribed).IsFalse(); + + _ = result.Value; + + await Assert.That(subscribed).IsTrue(); result.Dispose(); } /// - /// Tests that ToProperty with Expression creates a valid helper. + /// Tests that ToProperty with Expression creates a valid helper. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ToProperty_WithExpression_CreatesValidHelper() { @@ -164,34 +207,34 @@ public async Task ToProperty_WithExpression_CreatesValidHelper() } /// - /// Tests that ToProperty with Expression and initial value creates a helper with initial value. + /// Tests that ToProperty with Expression throws ArgumentNullException when property is null. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithExpressionAndInitialValue_CreatesHelperWithInitialValue() + public async Task ToProperty_WithExpression_ThrowsOnNullProperty() { var source = new TestReactiveObject(); - var observable = Observable.Never(); - - var result = observable.ToProperty(source, x => x.TestProperty, "initial", scheduler: ImmediateScheduler.Instance); - - await Assert.That(result).IsNotNull(); - await Assert.That(result.Value).IsEqualTo("initial"); + var observable = Observable.Return("test"); - result.Dispose(); + await Assert.That(() => observable.ToProperty(source, (Expression>)null!)) + .Throws(); } /// - /// Tests that ToProperty with string name and initial value creates a helper with initial value. + /// Tests that ToProperty with Expression and initial value creates a helper with initial value. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithStringNameAndInitialValue_CreatesHelperWithInitialValue() + public async Task ToProperty_WithExpressionAndInitialValue_CreatesHelperWithInitialValue() { var source = new TestReactiveObject(); var observable = Observable.Never(); - var result = observable.ToProperty(source, nameof(source.TestProperty), "initial", scheduler: ImmediateScheduler.Instance); + var result = observable.ToProperty( + source, + x => x.TestProperty, + "initial", + scheduler: ImmediateScheduler.Instance); await Assert.That(result).IsNotNull(); await Assert.That(result.Value).IsEqualTo("initial"); @@ -200,61 +243,44 @@ public async Task ToProperty_WithStringNameAndInitialValue_CreatesHelperWithInit } /// - /// Tests that ToProperty with deferSubscription defers subscription until Value is accessed. + /// Tests that ToProperty with Expression and initial value throws ArgumentNullException when property is null. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithDeferSubscription_DefersSubscriptionUntilValueAccessed() + public async Task ToProperty_WithExpressionAndInitialValue_ThrowsOnNullProperty() { var source = new TestReactiveObject(); - var subscribed = false; - var observable = Observable.Create(observer => - { - subscribed = true; - observer.OnNext("test"); - observer.OnCompleted(); - return Disposable.Empty; - }); - - var result = observable.ToProperty(source, x => x.TestProperty, deferSubscription: true, scheduler: ImmediateScheduler.Instance); - - await Assert.That(subscribed).IsFalse(); - - _ = result.Value; - - await Assert.That(subscribed).IsTrue(); + var observable = Observable.Return("test"); - result.Dispose(); + await Assert.That(() => observable.ToProperty( + source, + (Expression>)null!, + "initial")) + .Throws(); } /// - /// Tests that ToProperty without deferSubscription subscribes immediately. + /// Tests that ToProperty with Expression and out parameter returns the helper through out parameter. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ToProperty_WithoutDeferSubscription_SubscribesImmediately() + public async Task ToProperty_WithExpressionAndOut_ReturnsHelperThroughOutParameter() { var source = new TestReactiveObject(); - var subscribed = false; - var observable = Observable.Create(observer => - { - subscribed = true; - observer.OnNext("test"); - observer.OnCompleted(); - return Disposable.Empty; - }).ObserveOn(ImmediateScheduler.Instance); + var observable = Observable.Return("test"); - var result = observable.ToProperty(source, x => x.TestProperty, deferSubscription: false, scheduler: ImmediateScheduler.Instance); + var result = observable.ToProperty(source, x => x.TestProperty, out var outResult); - await Assert.That(subscribed).IsTrue(); + await Assert.That(result).IsNotNull(); + await Assert.That(outResult).IsEqualTo(result); result.Dispose(); } /// - /// Tests that ToProperty with getInitialValue function uses the function. + /// Tests that ToProperty with getInitialValue function uses the function. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ToProperty_WithGetInitialValue_UsesFunction() { @@ -279,156 +305,139 @@ public async Task ToProperty_WithGetInitialValue_UsesFunction() } /// - /// Tests that internal ObservableToProperty with Expression and getInitialValue creates helper and raises notifications. + /// Tests that ToProperty without deferSubscription subscribes immediately. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ObservableToProperty_WithExpressionAndGetInitialValue_CreatesHelperAndRaisesNotifications() + public async Task ToProperty_WithoutDeferSubscription_SubscribesImmediately() { var source = new TestReactiveObject(); - var observable = Observable.Return("newValue").ObserveOn(ImmediateScheduler.Instance); - var changingFired = false; - var changedFired = false; - - source.PropertyChanging += (s, e) => - { - if (e.PropertyName == nameof(source.TestProperty)) - { - changingFired = true; - } - }; - - source.PropertyChanged += (s, e) => + var subscribed = false; + var observable = Observable.Create(observer => { - if (e.PropertyName == nameof(source.TestProperty)) - { - changedFired = true; - } - }; + subscribed = true; + observer.OnNext("test"); + observer.OnCompleted(); + return Disposable.Empty; + }).ObserveOn(ImmediateScheduler.Instance); - var result = source.ObservableToProperty( - observable, - x => x.TestProperty, - () => "initial", - scheduler: ImmediateScheduler.Instance); + var result = observable.ToProperty(source, x => x.TestProperty, false, ImmediateScheduler.Instance); - await Assert.That(result).IsNotNull(); - await Assert.That(result.Value).IsEqualTo("newValue"); - await Assert.That(changingFired).IsTrue(); - await Assert.That(changedFired).IsTrue(); + await Assert.That(subscribed).IsTrue(); result.Dispose(); } /// - /// Tests that internal ObservableToProperty with Expression without initialValue uses default. + /// Tests that ToProperty with string name throws ArgumentException when property name is empty. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ObservableToProperty_WithExpressionNoInitialValue_UsesDefault() + public async Task ToProperty_WithStringName_ThrowsOnEmptyPropertyName() { var source = new TestReactiveObject(); - var observable = Observable.Never(); + var observable = Observable.Return("test"); - var result = source.ObservableToProperty( - observable, - x => x.TestProperty, - scheduler: ImmediateScheduler.Instance); + await Assert.That(() => observable.ToProperty(source, string.Empty)) + .Throws(); + } - await Assert.That(result).IsNotNull(); - await Assert.That(result.Value).IsNull(); + /// + /// Tests that ToProperty with string name throws ArgumentNullException when observable is null. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ToProperty_WithStringName_ThrowsOnNullObservable() + { + var source = new TestReactiveObject(); - result.Dispose(); + await Assert.That(() => ((IObservable)null!).ToProperty(source, "TestProperty")) + .Throws(); } /// - /// Tests that internal ObservableToProperty with string name and getInitialValue creates helper. + /// Tests that ToProperty with string name throws ArgumentException when property name is null. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ObservableToProperty_WithStringNameAndGetInitialValue_CreatesHelper() + public async Task ToProperty_WithStringName_ThrowsOnNullPropertyName() { var source = new TestReactiveObject(); - var observable = Observable.Return("newValue").ObserveOn(ImmediateScheduler.Instance); - var changingFired = false; - var changedFired = false; + var observable = Observable.Return("test"); - source.PropertyChanging += (s, e) => - { - if (e.PropertyName == nameof(source.TestProperty)) - { - changingFired = true; - } - }; + await Assert.That(() => observable.ToProperty(source, (string)null!)) + .Throws(); + } - source.PropertyChanged += (s, e) => - { - if (e.PropertyName == nameof(source.TestProperty)) - { - changedFired = true; - } - }; + /// + /// Tests that ToProperty with string name throws ArgumentNullException when target is null. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ToProperty_WithStringName_ThrowsOnNullTarget() + { + var observable = Observable.Return("test"); - var result = source.ObservableToProperty( - observable, - nameof(source.TestProperty), - () => "initial", - scheduler: ImmediateScheduler.Instance); + await Assert.That(() => observable.ToProperty(null!, "TestProperty")) + .Throws(); + } - await Assert.That(result).IsNotNull(); - await Assert.That(result.Value).IsEqualTo("newValue"); - await Assert.That(changingFired).IsTrue(); - await Assert.That(changedFired).IsTrue(); + /// + /// Tests that ToProperty with string name throws ArgumentException when property name is whitespace. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ToProperty_WithStringName_ThrowsOnWhitespacePropertyName() + { + var source = new TestReactiveObject(); + var observable = Observable.Return("test"); - result.Dispose(); + await Assert.That(() => observable.ToProperty(source, " ")) + .Throws(); } /// - /// Tests that internal ObservableToProperty with string name without initialValue uses default. + /// Tests that ToProperty with string name and initial value creates a helper with initial value. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ObservableToProperty_WithStringNameNoInitialValue_UsesDefault() + public async Task ToProperty_WithStringNameAndInitialValue_CreatesHelperWithInitialValue() { var source = new TestReactiveObject(); var observable = Observable.Never(); - var result = source.ObservableToProperty( - observable, + var result = observable.ToProperty( + source, nameof(source.TestProperty), + "initial", scheduler: ImmediateScheduler.Instance); await Assert.That(result).IsNotNull(); - await Assert.That(result.Value).IsNull(); + await Assert.That(result.Value).IsEqualTo("initial"); result.Dispose(); } /// - /// Tests that internal ObservableToProperty with Expression extracts correct property name. + /// Tests that ToProperty with string name and out parameter returns the helper through out parameter. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ObservableToProperty_WithExpression_ExtractsCorrectPropertyName() + public async Task ToProperty_WithStringNameAndOut_ReturnsHelperThroughOutParameter() { var source = new TestReactiveObject(); - var observable = Observable.Return("test").ObserveOn(ImmediateScheduler.Instance); - string? capturedPropertyName = null; - - source.PropertyChanged += (s, e) => capturedPropertyName = e.PropertyName; + var observable = Observable.Return("test"); - var result = source.ObservableToProperty( - observable, - x => x.TestProperty, - scheduler: ImmediateScheduler.Instance); + var result = observable.ToProperty(source, nameof(source.TestProperty), out var outResult); - await Assert.That(capturedPropertyName).IsEqualTo(nameof(source.TestProperty)); + await Assert.That(result).IsNotNull(); + await Assert.That(outResult).IsEqualTo(result); result.Dispose(); } /// - /// Test reactive object for testing. + /// Test reactive object for testing. /// private class TestReactiveObject : ReactiveObject { diff --git a/src/tests/ReactiveUI.Tests/ObservableMixinsTest.cs b/src/tests/ReactiveUI.Tests/ObservableMixinsTest.cs index cea2f7618a..ef0c942dcb 100644 --- a/src/tests/ReactiveUI.Tests/ObservableMixinsTest.cs +++ b/src/tests/ReactiveUI.Tests/ObservableMixinsTest.cs @@ -6,60 +6,60 @@ namespace ReactiveUI.Tests; /// -/// Tests for . +/// Tests for . /// public class ObservableMixinsTest { /// - /// Tests that WhereNotNull filters out null values. + /// Tests that WhereNotNull emits all non-null values. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task WhereNotNull_FiltersNullValues() + public async Task WhereNotNull_EmitsAllNonNullValues() { - var subject = new Subject(); - var results = new List(); + var subject = new Subject(); + var results = new List(); subject.WhereNotNull().ObserveOn(ImmediateScheduler.Instance).Subscribe(results.Add); - subject.OnNext("value1"); - subject.OnNext(null); - subject.OnNext("value2"); - subject.OnNext(null); - subject.OnNext("value3"); + subject.OnNext(1); + subject.OnNext(2); + subject.OnNext(3); await Assert.That(results).Count().IsEqualTo(3); - await Assert.That(results[0]).IsEqualTo("value1"); - await Assert.That(results[1]).IsEqualTo("value2"); - await Assert.That(results[2]).IsEqualTo("value3"); + await Assert.That(results[0]).IsEqualTo(1); + await Assert.That(results[1]).IsEqualTo(2); + await Assert.That(results[2]).IsEqualTo(3); } /// - /// Tests that WhereNotNull emits all non-null values. + /// Tests that WhereNotNull filters out null values. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task WhereNotNull_EmitsAllNonNullValues() + public async Task WhereNotNull_FiltersNullValues() { - var subject = new Subject(); - var results = new List(); + var subject = new Subject(); + var results = new List(); subject.WhereNotNull().ObserveOn(ImmediateScheduler.Instance).Subscribe(results.Add); - subject.OnNext(1); - subject.OnNext(2); - subject.OnNext(3); + subject.OnNext("value1"); + subject.OnNext(null); + subject.OnNext("value2"); + subject.OnNext(null); + subject.OnNext("value3"); await Assert.That(results).Count().IsEqualTo(3); - await Assert.That(results[0]).IsEqualTo(1); - await Assert.That(results[1]).IsEqualTo(2); - await Assert.That(results[2]).IsEqualTo(3); + await Assert.That(results[0]).IsEqualTo("value1"); + await Assert.That(results[1]).IsEqualTo("value2"); + await Assert.That(results[2]).IsEqualTo("value3"); } /// - /// Tests that WhereNotNull emits nothing when only nulls are sent. + /// Tests that WhereNotNull emits nothing when only nulls are sent. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhereNotNull_WithOnlyNulls_EmitsNothing() { @@ -76,9 +76,9 @@ public async Task WhereNotNull_WithOnlyNulls_EmitsNothing() } /// - /// Tests that WhereNotNull works with reference types. + /// Tests that WhereNotNull works with reference types. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhereNotNull_WorksWithReferenceTypes() { @@ -99,7 +99,7 @@ public async Task WhereNotNull_WorksWithReferenceTypes() } /// - /// Test class for reference type testing. + /// Test class for reference type testing. /// private class TestClass { diff --git a/src/tests/ReactiveUI.Tests/ObservedChanged/Mocks/NewGameViewModel.cs b/src/tests/ReactiveUI.Tests/ObservedChanged/Mocks/NewGameViewModel.cs index a68f6a180a..8bcff803c4 100644 --- a/src/tests/ReactiveUI.Tests/ObservedChanged/Mocks/NewGameViewModel.cs +++ b/src/tests/ReactiveUI.Tests/ObservedChanged/Mocks/NewGameViewModel.cs @@ -4,13 +4,12 @@ // See the LICENSE file in the project root for full license information. using System.Security.Cryptography; - using DynamicData.Binding; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.ObservedChanged.Mocks; /// -/// A sample view model that implements a game. +/// A sample view model that implements a game. /// /// public class NewGameViewModel : ReactiveObject @@ -18,7 +17,7 @@ public class NewGameViewModel : ReactiveObject private string? _newPlayerName; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public NewGameViewModel() { @@ -26,76 +25,76 @@ public NewGameViewModel() var canStart = Players.ToObservableChangeSet().CountChanged().Select(_ => Players.Count >= 3); StartGame = ReactiveCommand.Create( - () => { }, - canStart); + () => { }, + canStart); RandomizeOrder = ReactiveCommand.Create( - () => - { - using (Players.SuspendNotifications()) - { - var list = Players.ToList(); - ShuffleCrypto(list); - Players.Clear(); - Players.AddRange(list); - } - }, - canStart); + () => + { + using (Players.SuspendNotifications()) + { + var list = Players.ToList(); + ShuffleCrypto(list); + Players.Clear(); + Players.AddRange(list); + } + }, + canStart); RemovePlayer = ReactiveCommand.Create(player => Players.Remove(player)); var canAddPlayer = this.WhenAnyValue( - x => x.Players.Count, - x => x.NewPlayerName, - (count, newPlayerName) => - count < 7 && !string.IsNullOrWhiteSpace(newPlayerName) && - !Players.Contains(newPlayerName)); + x => x.Players.Count, + x => x.NewPlayerName, + (count, newPlayerName) => + count < 7 && !string.IsNullOrWhiteSpace(newPlayerName) && + !Players.Contains(newPlayerName)); AddPlayer = ReactiveCommand.Create( - () => - { - if (NewPlayerName is null) - { - throw new InvalidOperationException("NewPlayerName is null"); - } + () => + { + if (NewPlayerName is null) + { + throw new InvalidOperationException("NewPlayerName is null"); + } - Players.Add(NewPlayerName.Trim()); - NewPlayerName = string.Empty; - }, - canAddPlayer); + Players.Add(NewPlayerName.Trim()); + NewPlayerName = string.Empty; + }, + canAddPlayer); } /// - /// Gets the players collection. + /// Gets the add player command. /// - public ObservableCollectionExtended Players { get; } + public ReactiveCommand AddPlayer { get; } /// - /// Gets the add player command. + /// Gets the players collection. /// - public ReactiveCommand AddPlayer { get; } + public ObservableCollectionExtended Players { get; } /// - /// Gets the remove player command. + /// Gets the randomize order command. /// - public ReactiveCommand RemovePlayer { get; } + public ReactiveCommand RandomizeOrder { get; } /// - /// Gets the start game command. + /// Gets the remove player command. /// - public ReactiveCommand StartGame { get; } + public ReactiveCommand RemovePlayer { get; } /// - /// Gets the randomize order command. + /// Gets the start game command. /// - public ReactiveCommand RandomizeOrder { get; } + public ReactiveCommand StartGame { get; } /// - /// Gets or sets the new player name. + /// Gets or sets the new player name. /// public string? NewPlayerName { get => _newPlayerName; set => this.RaiseAndSetIfChanged( - ref _newPlayerName, - value); + ref _newPlayerName, + value); } private static void ShuffleCrypto(List list) diff --git a/src/tests/ReactiveUI.NonParallel.Tests/ObservedChanged/NewGameViewModelTests.cs b/src/tests/ReactiveUI.Tests/ObservedChanged/NewGameViewModelTests.cs similarity index 70% rename from src/tests/ReactiveUI.NonParallel.Tests/ObservedChanged/NewGameViewModelTests.cs rename to src/tests/ReactiveUI.Tests/ObservedChanged/NewGameViewModelTests.cs index ed51a44888..5ec76008ca 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/ObservedChanged/NewGameViewModelTests.cs +++ b/src/tests/ReactiveUI.Tests/ObservedChanged/NewGameViewModelTests.cs @@ -3,21 +3,23 @@ // 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.Core; +using ReactiveUI.Tests.ObservedChanged.Mocks; + +namespace ReactiveUI.Tests.ObservedChanged; public class NewGameViewModelTests { private readonly NewGameViewModel _viewmodel; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public NewGameViewModelTests() => _viewmodel = new NewGameViewModel(); /// - /// Tests that determines whether this instance [can add up to seven players]. + /// Tests that determines whether this instance [can add up to seven players]. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task CanAddUpToSevenPlayers() { diff --git a/src/tests/ReactiveUI.Tests/ObservedChanged/ObservedChangedMixinTest.cs b/src/tests/ReactiveUI.Tests/ObservedChanged/ObservedChangedMixinTest.cs index f574b22e62..9e44ffac5a 100644 --- a/src/tests/ReactiveUI.Tests/ObservedChanged/ObservedChangedMixinTest.cs +++ b/src/tests/ReactiveUI.Tests/ObservedChanged/ObservedChangedMixinTest.cs @@ -3,215 +3,188 @@ // 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.Reactive.Testing; +using ReactiveUI.Tests.ReactiveObjects.Mocks; +using ReactiveUI.Tests.Utilities; +using ReactiveUI.Tests.WhenAny.Mockups; -using ReactiveUI.Testing; - -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.ObservedChanged; /// -/// Tests for the ObservedChangedMixin. +/// Tests for the ObservedChangedMixin. /// public class ObservedChangedMixinTest { /// - /// Tests that getting the value should actually return the value. + /// Tests to make sure that BindTo can handle intermediate object switching. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueShouldActuallyReturnTheValue() + [TestExecutor] + public async Task BindToIsNotFooledByIntermediateObjectSwitching() { - var input = new[] { "Foo", "Bar", "Baz" }; - var output = new List(); + var scheduler = TestContext.Current!.GetScheduler(); + var input = new Subject(); + var fixture = new HostTestFixture { Child = new TestFixture() }; - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); + input.BindTo(fixture, static x => x.Child!.IsNotNullString); + + await Assert.That(fixture.Child.IsNotNullString).IsNull(); + + input.OnNext("Foo"); + + // ImmediateScheduler executes synchronously + await Assert.That(fixture.Child!.IsNotNullString).IsEqualTo("Foo"); - // ...whereas ObservableForProperty *is* guaranteed to. - fixture.ObservableForProperty(x => x.IsOnlyOneWord) - .Select(x => x.GetValue()) - .WhereNotNull() - .Subscribe(x => output.Add(x)); + fixture.Child = new TestFixture(); - foreach (var v in input) - { - fixture.IsOnlyOneWord = v; - } + // ImmediateScheduler executes synchronously + await Assert.That(fixture.Child!.IsNotNullString).IsEqualTo("Foo"); - scheduler.AdvanceToMs(1000); + input.OnNext("Bar"); - await input.AssertAreEqual(output); - }); + // ImmediateScheduler executes synchronously + await Assert.That(fixture.Child!.IsNotNullString).IsEqualTo("Bar"); } /// - /// Tests that getting the value should return the value from a path. + /// Runs a smoke test for the BindTo functionality. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueShouldReturnTheValueFromAPath() + [TestExecutor] + public async Task BindToSmokeTest() { - var input = new HostTestFixture - { - Child = new TestFixture { IsNotNullString = "Foo" }, - }; + var scheduler = TestContext.Current!.GetScheduler(); + var input = new Subject(); + var fixture = new HostTestFixture { Child = new TestFixture() }; - Expression> expression = static x => x!.Child!.IsNotNullString!; - var fixture = new ObservedChange(input, expression.Body, null); + input.BindTo(fixture, static x => x.Child!.IsNotNullString); - await Assert.That(fixture.GetValue()).IsEqualTo("Foo"); + await Assert.That(fixture.Child.IsNotNullString).IsNull(); + + input.OnNext("Foo"); + + // ImmediateScheduler executes synchronously + await Assert.That(fixture.Child.IsNotNullString).IsEqualTo("Foo"); + + input.OnNext("Bar"); + + // ImmediateScheduler executes synchronously + await Assert.That(fixture.Child.IsNotNullString).IsEqualTo("Bar"); } /// - /// Runs a smoke test that sets the value path. + /// Tests to make sure that BindTo can handle Stack Overflow conditions. /// - /// A representing the asynchronous operation. [Test] - public async Task SetValuePathSmokeTest() + public void BindToStackOverFlowTest() { - var output = new HostTestFixture - { - Child = new TestFixture { IsNotNullString = "Foo" }, - }; - - Expression> expression = static x => x.IsOnlyOneWord!; - var fixture = new ObservedChange(new TestFixture { IsOnlyOneWord = "Bar" }, expression.Body, null); - - fixture.SetValueToProperty(output, static x => x.Child!.IsNotNullString); - await Assert.That(output.Child.IsNotNullString).IsEqualTo("Bar"); + // Before the code changes packed in the same commit + // as this test the test would go into an infinite + // event storm. The critical issue is that the + // property StackOverflowTrigger will clone the + // value before setting it. + // + // If this test executes through without hanging then + // the problem has been fixed. + var fixtureA = new TestFixture(); + var fixtureB = new TestFixture(); + + var source = new BehaviorSubject>([]); + + source.BindTo(fixtureA, static x => x.StackOverflowTrigger); } /// - /// Runs a smoke test for the BindTo functionality. + /// Tests to make sure that Disposing disconnects BindTo and updates are no longer pushed. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task BindToSmokeTest() => - await new TestScheduler().With(static async scheduler => - { - var input = new ScheduledSubject(scheduler); - var fixture = new HostTestFixture { Child = new TestFixture() }; - - input.BindTo(fixture, static x => x.Child!.IsNotNullString); - - await Assert.That(fixture.Child.IsNotNullString).IsNull(); - - input.OnNext("Foo"); - scheduler.Start(); - await Assert.That(fixture.Child.IsNotNullString).IsEqualTo("Foo"); + [TestExecutor] + public async Task DisposingDisconnectsTheBindTo() + { + var scheduler = TestContext.Current!.GetScheduler(); + var input = new Subject(); + var fixture = new HostTestFixture { Child = new TestFixture() }; - input.OnNext("Bar"); - scheduler.Start(); - await Assert.That(fixture.Child.IsNotNullString).IsEqualTo("Bar"); - }); + var subscription = input.BindTo(fixture, static x => x.Child!.IsNotNullString); - /// - /// Tests to make sure that Disposing disconnects BindTo and updates are no longer pushed. - /// - /// A representing the asynchronous operation. - [Test] - public async Task DisposingDisconnectsTheBindTo() => - await new TestScheduler().With(static async scheduler => - { - var input = new ScheduledSubject(scheduler); - var fixture = new HostTestFixture { Child = new TestFixture() }; + await Assert.That(fixture.Child.IsNotNullString).IsNull(); - var subscription = input.BindTo(fixture, static x => x.Child!.IsNotNullString); + input.OnNext("Foo"); - await Assert.That(fixture.Child.IsNotNullString).IsNull(); + // ImmediateScheduler executes synchronously + await Assert.That(fixture.Child.IsNotNullString).IsEqualTo("Foo"); - input.OnNext("Foo"); - scheduler.Start(); - await Assert.That(fixture.Child.IsNotNullString).IsEqualTo("Foo"); + subscription.Dispose(); - subscription.Dispose(); + input.OnNext("Bar"); - input.OnNext("Bar"); - scheduler.Start(); - await Assert.That(fixture.Child.IsNotNullString).IsEqualTo("Foo"); - }); + // ImmediateScheduler executes synchronously + await Assert.That(fixture.Child.IsNotNullString).IsEqualTo("Foo"); + } /// - /// Tests to make sure that BindTo can handle intermediate object switching. + /// Tests that GetPropertyName throws for null item. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task BindToIsNotFooledByIntermediateObjectSwitching() => - await new TestScheduler().With(static async scheduler => - { - var input = new ScheduledSubject(scheduler); - var fixture = new HostTestFixture { Child = new TestFixture() }; - - input.BindTo(fixture, static x => x.Child!.IsNotNullString); - - await Assert.That(fixture.Child.IsNotNullString).IsNull(); + public async Task GetPropertyName_NullItem_Throws() + { + IObservedChange item = null!; - input.OnNext("Foo"); - scheduler.Start(); - await Assert.That(fixture.Child!.IsNotNullString).IsEqualTo("Foo"); + await Assert.That(() => item.GetPropertyName()) + .Throws(); + } - fixture.Child = new TestFixture(); - scheduler.Start(); - await Assert.That(fixture.Child!.IsNotNullString).IsEqualTo("Foo"); + /// + /// Tests that GetPropertyName returns the property name. + /// + /// A representing the asynchronous operation. + [Test] + public async Task GetPropertyName_ReturnsPropertyName() + { + var input = new TestFixture { IsOnlyOneWord = "Foo" }; + Expression> expression = static x => x.IsOnlyOneWord!; + var fixture = new ObservedChange(input, expression.Body, null); - input.OnNext("Bar"); - scheduler.Start(); - await Assert.That(fixture.Child!.IsNotNullString).IsEqualTo("Bar"); - }); + await Assert.That(fixture.GetPropertyName()).IsEqualTo("IsOnlyOneWord"); + } /// - /// Tests to make sure that BindTo can handle Stack Overflow conditions. + /// Tests that GetValue throws for null item. /// + /// A representing the asynchronous operation. [Test] - public void BindToStackOverFlowTest() => - new TestScheduler().With(static _ => - { - // Before the code changes packed in the same commit - // as this test the test would go into an infinite - // event storm. The critical issue is that the - // property StackOverflowTrigger will clone the - // value before setting it. - // - // If this test executes through without hanging then - // the problem has been fixed. - var fixtureA = new TestFixture(); - var fixtureB = new TestFixture(); - - var source = new BehaviorSubject>([]); - - source.BindTo(fixtureA, static x => x.StackOverflowTrigger); - }); + public async Task GetValue_NullItem_Throws() + { + IObservedChange item = null!; + + await Assert.That(() => item.GetValue()) + .Throws(); + } /// - /// Tests that GetValueOrDefault returns value when property is not null. + /// Tests that GetValueOrDefault throws for null item. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueOrDefault_WithValue_ReturnsValue() + public async Task GetValueOrDefault_NullItem_Throws() { - var input = new HostTestFixture - { - Child = new TestFixture { IsNotNullString = "Foo" }, - }; - - Expression> expression = static x => x!.Child!.IsNotNullString!; - var fixture = new ObservedChange(input, expression.Body, null); + IObservedChange item = null!; - await Assert.That(fixture.GetValueOrDefault()).IsEqualTo("Foo"); + await Assert.That(() => item.GetValueOrDefault()) + .Throws(); } /// - /// Tests that GetValueOrDefault returns default when property chain is null. + /// Tests that GetValueOrDefault returns default when property chain is null. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task GetValueOrDefault_WithNullInChain_ReturnsDefault() { - var input = new HostTestFixture - { - Child = null, - }; + var input = new HostTestFixture { Child = null }; Expression> expression = static x => x!.Child!.IsNotNullString!; var fixture = new ObservedChange(input, expression.Body, null); @@ -220,85 +193,108 @@ public async Task GetValueOrDefault_WithNullInChain_ReturnsDefault() } /// - /// Tests that GetValueOrDefault throws for null item. + /// Tests that GetValueOrDefault returns value when property is not null. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueOrDefault_NullItem_Throws() + public async Task GetValueOrDefault_WithValue_ReturnsValue() { - IObservedChange item = null!; + var input = new HostTestFixture { Child = new TestFixture { IsNotNullString = "Foo" } }; - await Assert.That(() => item.GetValueOrDefault()) - .Throws(); + Expression> expression = static x => x!.Child!.IsNotNullString!; + var fixture = new ObservedChange(input, expression.Body, null); + + await Assert.That(fixture.GetValueOrDefault()).IsEqualTo("Foo"); } /// - /// Tests that Value extension method converts changes to values. + /// Tests that getting the value should actually return the value. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task Value_ConvertsChangesToValues() + [TestExecutor] + public async Task GetValueShouldActuallyReturnTheValue() { + var scheduler = TestContext.Current!.GetScheduler(); var input = new[] { "Foo", "Bar", "Baz" }; var output = new List(); - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); + var fixture = new TestFixture(); - fixture.ObservableForProperty(x => x.IsOnlyOneWord) - .Value() - .WhereNotNull() - .Subscribe(x => output.Add(x)); + // ...whereas ObservableForProperty *is* guaranteed to. + fixture.ObservableForProperty(x => x.IsOnlyOneWord) + .Select(x => x.GetValue()) + .WhereNotNull() + .Subscribe(x => output.Add(x)); - foreach (var v in input) - { - fixture.IsOnlyOneWord = v; - } - - scheduler.AdvanceToMs(1000); + foreach (var v in input) + { + fixture.IsOnlyOneWord = v; + } - await input.AssertAreEqual(output); - }); + // ImmediateScheduler executes synchronously + await input.AssertAreEqual(output); } /// - /// Tests that GetPropertyName returns the property name. + /// Tests that getting the value should return the value from a path. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetPropertyName_ReturnsPropertyName() + public async Task GetValueShouldReturnTheValueFromAPath() { - var input = new TestFixture { IsOnlyOneWord = "Foo" }; - Expression> expression = static x => x.IsOnlyOneWord!; - var fixture = new ObservedChange(input, expression.Body, null); + var input = new HostTestFixture { Child = new TestFixture { IsNotNullString = "Foo" } }; - await Assert.That(fixture.GetPropertyName()).IsEqualTo("IsOnlyOneWord"); + Expression> expression = static x => x!.Child!.IsNotNullString!; + var fixture = new ObservedChange(input, expression.Body, null); + + await Assert.That(fixture.GetValue()).IsEqualTo("Foo"); } /// - /// Tests that GetPropertyName throws for null item. + /// Runs a smoke test that sets the value path. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetPropertyName_NullItem_Throws() + public async Task SetValuePathSmokeTest() { - IObservedChange item = null!; + var output = new HostTestFixture { Child = new TestFixture { IsNotNullString = "Foo" } }; - await Assert.That(() => item.GetPropertyName()) - .Throws(); + Expression> expression = static x => x.IsOnlyOneWord!; + var fixture = new ObservedChange( + new TestFixture { IsOnlyOneWord = "Bar" }, + expression.Body, + null); + + fixture.SetValueToProperty(output, static x => x.Child!.IsNotNullString); + await Assert.That(output.Child.IsNotNullString).IsEqualTo("Bar"); } /// - /// Tests that GetValue throws for null item. + /// Tests that Value extension method converts changes to values. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValue_NullItem_Throws() + [TestExecutor] + public async Task Value_ConvertsChangesToValues() { - IObservedChange item = null!; + var scheduler = TestContext.Current!.GetScheduler(); + var input = new[] { "Foo", "Bar", "Baz" }; + var output = new List(); - await Assert.That(() => item.GetValue()) - .Throws(); + var fixture = new TestFixture(); + + fixture.ObservableForProperty(x => x.IsOnlyOneWord) + .Value() + .WhereNotNull() + .Subscribe(x => output.Add(x)); + + foreach (var v in input) + { + fixture.IsOnlyOneWord = v; + } + + // ImmediateScheduler executes synchronously + await input.AssertAreEqual(output); } } diff --git a/src/tests/ReactiveUI.Tests/OrderedComparerTest.cs b/src/tests/ReactiveUI.Tests/OrderedComparerTest.cs index 5020cab016..a6923d9131 100644 --- a/src/tests/ReactiveUI.Tests/OrderedComparerTest.cs +++ b/src/tests/ReactiveUI.Tests/OrderedComparerTest.cs @@ -6,28 +6,42 @@ namespace ReactiveUI.Tests; /// -/// Tests for and . +/// Tests for and . /// public class OrderedComparerTest { /// - /// Tests that For with enumerable returns a builder. + /// Tests that builder OrderBy creates a comparer. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task For_WithEnumerable_ReturnsBuilder() + public async Task Builder_OrderBy_CreatesComparer() { - var items = new[] { new TestClass { Value = 1 } }; + var builder = OrderedComparer.For(); - var builder = OrderedComparer.For(items); + var comparer = builder.OrderBy(x => x.Value); - await Assert.That(builder).IsNotNull(); + await Assert.That(comparer).IsNotNull(); + } + + /// + /// Tests that builder OrderByDescending creates a comparer. + /// + /// A representing the asynchronous operation. + [Test] + public async Task Builder_OrderByDescending_CreatesComparer() + { + var builder = OrderedComparer.For(); + + var comparer = builder.OrderByDescending(x => x.Value); + + await Assert.That(comparer).IsNotNull(); } /// - /// Tests that For without argument returns a builder. + /// Tests that For without argument returns a builder. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task For_Generic_ReturnsBuilder() { @@ -37,9 +51,23 @@ public async Task For_Generic_ReturnsBuilder() } /// - /// Tests that OrderBy creates a comparer that sorts ascending. + /// Tests that For with enumerable returns a builder. + /// + /// A representing the asynchronous operation. + [Test] + public async Task For_WithEnumerable_ReturnsBuilder() + { + var items = new[] { new TestClass { Value = 1 } }; + + var builder = OrderedComparer.For(items); + + await Assert.That(builder).IsNotNull(); + } + + /// + /// Tests that OrderBy creates a comparer that sorts ascending. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task OrderBy_SortsAscending() { @@ -53,9 +81,9 @@ public async Task OrderBy_SortsAscending() } /// - /// Tests that OrderBy with custom comparer works correctly. + /// Tests that OrderBy with custom comparer works correctly. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task OrderBy_WithCustomComparer_UsesCustomComparer() { @@ -70,9 +98,9 @@ public async Task OrderBy_WithCustomComparer_UsesCustomComparer() } /// - /// Tests that OrderByDescending creates a comparer that sorts descending. + /// Tests that OrderByDescending creates a comparer that sorts descending. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task OrderByDescending_SortsDescending() { @@ -86,9 +114,9 @@ public async Task OrderByDescending_SortsDescending() } /// - /// Tests that OrderByDescending with custom comparer works correctly. + /// Tests that OrderByDescending with custom comparer works correctly. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task OrderByDescending_WithCustomComparer_UsesCustomComparer() { @@ -103,35 +131,7 @@ public async Task OrderByDescending_WithCustomComparer_UsesCustomComparer() } /// - /// Tests that builder OrderBy creates a comparer. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Builder_OrderBy_CreatesComparer() - { - var builder = OrderedComparer.For(); - - var comparer = builder.OrderBy(x => x.Value); - - await Assert.That(comparer).IsNotNull(); - } - - /// - /// Tests that builder OrderByDescending creates a comparer. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Builder_OrderByDescending_CreatesComparer() - { - var builder = OrderedComparer.For(); - - var comparer = builder.OrderByDescending(x => x.Value); - - await Assert.That(comparer).IsNotNull(); - } - - /// - /// Test class for comparison testing. + /// Test class for comparison testing. /// private class TestClass { diff --git a/src/tests/ReactiveUI.Tests/PlatformRegistrationsTest.cs b/src/tests/ReactiveUI.Tests/PlatformRegistrationsTest.cs deleted file mode 100644 index 83886122ea..0000000000 --- a/src/tests/ReactiveUI.Tests/PlatformRegistrationsTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2025 .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; - -/// -/// Tests for . -/// -public class PlatformRegistrationsTest -{ - /// - /// Tests that Register throws for null registerFunction. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Register_NullRegisterFunction_Throws() - { - var registrations = new PlatformRegistrations(); - - await Assert.That(() => registrations.Register(null!)) - .Throws(); - } - - /// - /// Tests that Register calls registerFunction for ComponentModelTypeConverter. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Register_RegistersComponentModelTypeConverter() - { - var registrations = new PlatformRegistrations(); - var registered = new List<(Type ServiceType, object Instance)>(); - - registrations.Register((factory, serviceType) => - { - registered.Add((serviceType, factory())); - }); - - await Assert.That(registered).Count().IsGreaterThan(0); - var typeConverterRegistration = registered.FirstOrDefault(x => x.ServiceType == typeof(IBindingTypeConverter)); - await Assert.That(typeConverterRegistration.Instance).IsNotNull(); - await Assert.That(typeConverterRegistration.Instance).IsTypeOf(); - } - - /// - /// Tests that Register completes without throwing. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Register_CompletesSuccessfully() - { - var registrations = new PlatformRegistrations(); - - registrations.Register((factory, serviceType) => { }); - - await Task.CompletedTask; - } -} diff --git a/src/tests/ReactiveUI.Tests/Properties/Resources.Designer.cs b/src/tests/ReactiveUI.Tests/Properties/Resources.Designer.cs index ecd6dde0bb..47a01570fa 100644 --- a/src/tests/ReactiveUI.Tests/Properties/Resources.Designer.cs +++ b/src/tests/ReactiveUI.Tests/Properties/Resources.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/src/tests/ReactiveUI.Tests/Properties/Resources.resx b/src/tests/ReactiveUI.Tests/Properties/Resources.resx index 394f38f4f2..f950e9ce29 100644 --- a/src/tests/ReactiveUI.Tests/Properties/Resources.resx +++ b/src/tests/ReactiveUI.Tests/Properties/Resources.resx @@ -59,46 +59,47 @@ : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> - - + + - + - - - - + + + + - - + + - - + + - - - - + + + + - + - + @@ -112,10 +113,14 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Oops!? {0} is required. @@ -123,4 +128,4 @@ FromResource - \ No newline at end of file + diff --git a/src/tests/ReactiveUI.NonParallel.Tests/PropertyBinderImplementationTests.cs b/src/tests/ReactiveUI.Tests/PropertyBinderImplementationTests.cs similarity index 79% rename from src/tests/ReactiveUI.NonParallel.Tests/PropertyBinderImplementationTests.cs rename to src/tests/ReactiveUI.Tests/PropertyBinderImplementationTests.cs index 903e88dd4f..dd2ada8fe9 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/PropertyBinderImplementationTests.cs +++ b/src/tests/ReactiveUI.Tests/PropertyBinderImplementationTests.cs @@ -6,11 +6,35 @@ namespace ReactiveUI.Tests; /// -/// Tests for PropertyBinderImplementation covering edge cases and error paths. -/// These tests use Locator.CurrentMutable (static state) so must run in NonParallel test suite. +/// Tests for PropertyBinderImplementation covering edge cases and error paths. +/// These tests use Locator.CurrentMutable (static state) so must run in NonParallel test suite. /// public class PropertyBinderImplementationTests { + [Test] + public async Task Bind_WithDefaultTriggerUpdate_CreatesBinding() + { + // This test verifies the default ViewToViewModel trigger path + var viewModel = new TestViewModel { Count = 5 }; + var view = new TestView { ViewModel = viewModel }; + var fixture = new PropertyBinderImplementation(); + var signal = new Subject(); + + using var binding = fixture.Bind( + viewModel, + view, + vm => vm.Count, + v => v.CountText, + signal, + count => count.ToString(), + text => int.TryParse(text, out var n) ? n : 0); + + // Just verify binding was created + await Assert.That(binding).IsNotNull(); + + signal?.Dispose(); + } + [Test] public async Task Bind_WithNullReturningConverter_HandlesNullTmpValue() { @@ -34,85 +58,60 @@ public async Task Bind_WithNullReturningConverter_HandlesNullTmpValue() } [Test] - public async Task OneWayBind_WithFailingConverter_DoesNotUpdateView() + public async Task Bind_WithTriggerUpdateViewModelToView_ExercisesCodePath() { - var viewModel = new TestViewModel { Name = "Initial" }; + // This test exercises the TriggerUpdate.ViewModelToView code path + var viewModel = new TestViewModel { Count = 10 }; var view = new TestView { ViewModel = viewModel }; var fixture = new PropertyBinderImplementation(); + var signal = new Subject(); - var failingConverter = new FailingConverter(); - using var binding = fixture.OneWayBind( + // Using TriggerUpdate.ViewModelToView exercises the alternative binding implementation + using var binding = fixture.Bind( viewModel, view, - vm => vm.Name, - v => v.NameText, - vmToViewConverterOverride: failingConverter); - - var initialText = view.NameText; - - viewModel.Name = "Changed"; - - // View should not update when converter fails - await Assert.That(view.NameText).IsEqualTo(initialText); - } - - [Test] - public async Task OneWayBind_WithBindingHookReturningFalse_ReturnsEmptyObservable() - { - var viewModel = new TestViewModel { Name = "Test" }; - var view = new TestView { ViewModel = viewModel }; - var fixture = new PropertyBinderImplementation(); + vm => vm.Count, + v => v.CountText, + signal, + count => count.ToString(), + text => int.TryParse(text, out var n) ? n : 0, + TriggerUpdate.ViewModelToView); - var hook = new RejectingBindingHook(); - var previousHooks = Locator.Current.GetServices().ToList(); - Locator.CurrentMutable.RegisterConstant(hook); - try - { - using var binding = fixture.OneWayBind(viewModel, view, vm => vm.Name, v => v.NameText); + // Verify binding was created successfully + await Assert.That(binding).IsNotNull(); - // Binding should be created but produce no values - viewModel.Name = "Changed"; - await Assert.That(view.NameText).IsNotEqualTo("Changed"); - } - finally - { - Locator.CurrentMutable.UnregisterAll(); - foreach (var previousHook in previousHooks) - { - Locator.CurrentMutable.RegisterConstant(previousHook); - } - } + signal?.Dispose(); } [Test] - public async Task OneWayBind_WithSelector_AndBindingHookReturningFalse_ReturnsEmptyObservable() + public async Task BindImpl_WithBindingHookReturningFalse_ReturnsNull() { - var viewModel = new TestViewModel { Name = "Test" }; + var viewModel = new TestViewModel(); var view = new TestView { ViewModel = viewModel }; var fixture = new PropertyBinderImplementation(); var hook = new RejectingBindingHook(); - var previousHooks = Locator.Current.GetServices().ToList(); - Locator.CurrentMutable.RegisterConstant(hook); + var previousHooks = Splat.Locator.Current.GetServices().ToList(); + Splat.Locator.CurrentMutable.RegisterConstant(hook); try { - using var binding = fixture.OneWayBind( + var binding = fixture.Bind( viewModel, view, vm => vm.Name, v => v.NameText, - x => x?.ToUpper() ?? string.Empty); + (IObservable?)null, + null); - // Binding should be created but produce no values - viewModel.Name = "Changed"; - await Assert.That(view.NameText).IsNotEqualTo("CHANGED"); + // Should return null when binding is rejected + await Assert.That(binding).IsNull(); } finally { - Locator.CurrentMutable.UnregisterAll(); + Splat.Locator.CurrentMutable.UnregisterAll(); foreach (var previousHook in previousHooks) { - Locator.CurrentMutable.RegisterConstant(previousHook); + Splat.Locator.CurrentMutable.RegisterConstant(previousHook); } } } @@ -125,8 +124,8 @@ public async Task BindTo_WithBindingHookReturningFalse_ReturnsDisposableEmpty() var source = new Subject(); var hook = new RejectingBindingHook(); - var previousHooks = Locator.Current.GetServices().ToList(); - Locator.CurrentMutable.RegisterConstant(hook); + var previousHooks = Splat.Locator.Current.GetServices().ToList(); + Splat.Locator.CurrentMutable.RegisterConstant(hook); try { var disposable = fixture.BindTo(source, view, v => v.NameText); @@ -140,10 +139,10 @@ public async Task BindTo_WithBindingHookReturningFalse_ReturnsDisposableEmpty() } finally { - Locator.CurrentMutable.UnregisterAll(); + Splat.Locator.CurrentMutable.UnregisterAll(); foreach (var previousHook in previousHooks) { - Locator.CurrentMutable.RegisterConstant(previousHook); + Splat.Locator.CurrentMutable.RegisterConstant(previousHook); } } @@ -151,165 +150,188 @@ public async Task BindTo_WithBindingHookReturningFalse_ReturnsDisposableEmpty() } [Test] - public async Task BindImpl_WithBindingHookReturningFalse_ReturnsNull() + public async Task BindTo_WithTypeConversion_UsesConverter() { - var viewModel = new TestViewModel(); - var view = new TestView { ViewModel = viewModel }; + var target = new TestViewModel(); + var source = new BehaviorSubject(42); var fixture = new PropertyBinderImplementation(); - var hook = new RejectingBindingHook(); - var previousHooks = Locator.Current.GetServices().ToList(); - Locator.CurrentMutable.RegisterConstant(hook); + using var binding = fixture.BindTo(source, target, t => t.Name); + + // Should convert int to string + await Assert.That(target.Name).IsEqualTo("42"); + + source.OnNext(100); + await Assert.That(target.Name).IsEqualTo("100"); + + source?.Dispose(); + } + + [Test] + public async Task GetConverterForTypes_WithNullConverter_HandlesGracefully() + { + // Register a null converter to test null handling in type converter cache + var previousConverters = Splat.Locator.Current.GetServices().ToList(); + Splat.Locator.CurrentMutable.RegisterConstant(null!); try { - var binding = fixture.Bind( - viewModel, - view, - vm => vm.Name, - v => v.NameText, - (IObservable?)null, - null); + var converter = PropertyBinderImplementation.GetConverterForTypes(typeof(string), typeof(int)); - // Should return null when binding is rejected - await Assert.That(binding).IsNull(); + // Should handle null converters gracefully - built-in converter should still work + await Assert.That(converter).IsNotNull(); } finally { - Locator.CurrentMutable.UnregisterAll(); - foreach (var previousHook in previousHooks) + Splat.Locator.CurrentMutable.UnregisterAll(); + foreach (var previousConverter in previousConverters) { - Locator.CurrentMutable.RegisterConstant(previousHook); + Splat.Locator.CurrentMutable.RegisterConstant(previousConverter); } } } [Test] - public async Task Bind_WithTriggerUpdateViewModelToView_ExercisesCodePath() + public async Task OneWayBind_WithBindingHookReturningFalse_ReturnsEmptyObservable() { - // This test exercises the TriggerUpdate.ViewModelToView code path - var viewModel = new TestViewModel { Count = 10 }; + var viewModel = new TestViewModel { Name = "Test" }; var view = new TestView { ViewModel = viewModel }; var fixture = new PropertyBinderImplementation(); - var signal = new Subject(); - // Using TriggerUpdate.ViewModelToView exercises the alternative binding implementation - using var binding = fixture.Bind( - viewModel, - view, - vm => vm.Count, - v => v.CountText, - signal, - count => count.ToString(), - text => int.TryParse(text, out var n) ? n : 0, - TriggerUpdate.ViewModelToView); - - // Verify binding was created successfully - await Assert.That(binding).IsNotNull(); - - signal?.Dispose(); - } - - [Test] - public async Task GetConverterForTypes_WithNullConverter_HandlesGracefully() - { - // Register a null converter to test null handling in type converter cache - var previousConverters = Locator.Current.GetServices().ToList(); - Locator.CurrentMutable.RegisterConstant(null!); + var hook = new RejectingBindingHook(); + var previousHooks = Splat.Locator.Current.GetServices().ToList(); + Splat.Locator.CurrentMutable.RegisterConstant(hook); try { - var converter = PropertyBinderImplementation.GetConverterForTypes(typeof(string), typeof(int)); + using var binding = fixture.OneWayBind(viewModel, view, vm => vm.Name, v => v.NameText); - // Should handle null converters gracefully - built-in converter should still work - await Assert.That(converter).IsNotNull(); + // Binding should be created but produce no values + viewModel.Name = "Changed"; + await Assert.That(view.NameText).IsNotEqualTo("Changed"); } finally { - Locator.CurrentMutable.UnregisterAll(); - foreach (var previousConverter in previousConverters) + Splat.Locator.CurrentMutable.UnregisterAll(); + foreach (var previousHook in previousHooks) { - Locator.CurrentMutable.RegisterConstant(previousConverter); + Splat.Locator.CurrentMutable.RegisterConstant(previousHook); } } } [Test] - public async Task BindTo_WithTypeConversion_UsesConverter() + public async Task OneWayBind_WithFailingConverter_DoesNotUpdateView() { - var target = new TestViewModel(); - var source = new BehaviorSubject(42); + var viewModel = new TestViewModel { Name = "Initial" }; + var view = new TestView { ViewModel = viewModel }; var fixture = new PropertyBinderImplementation(); - using var binding = fixture.BindTo(source, target, t => t.Name); + var failingConverter = new FailingConverter(); + using var binding = fixture.OneWayBind( + viewModel, + view, + vm => vm.Name, + v => v.NameText, + vmToViewConverterOverride: failingConverter); - // Should convert int to string - await Assert.That(target.Name).IsEqualTo("42"); + var initialText = view.NameText; - source.OnNext(100); - await Assert.That(target.Name).IsEqualTo("100"); + viewModel.Name = "Changed"; - source?.Dispose(); + // View should not update when converter fails + await Assert.That(view.NameText).IsEqualTo(initialText); } [Test] - public async Task Bind_WithDefaultTriggerUpdate_CreatesBinding() + public async Task OneWayBind_WithSelector_AndBindingHookReturningFalse_ReturnsEmptyObservable() { - // This test verifies the default ViewToViewModel trigger path - var viewModel = new TestViewModel { Count = 5 }; + var viewModel = new TestViewModel { Name = "Test" }; var view = new TestView { ViewModel = viewModel }; var fixture = new PropertyBinderImplementation(); - var signal = new Subject(); - - using var binding = fixture.Bind( - viewModel, - view, - vm => vm.Count, - v => v.CountText, - signal, - count => count.ToString(), - text => int.TryParse(text, out var n) ? n : 0, - TriggerUpdate.ViewToViewModel); - // Just verify binding was created - await Assert.That(binding).IsNotNull(); + var hook = new RejectingBindingHook(); + var previousHooks = Splat.Locator.Current.GetServices().ToList(); + Splat.Locator.CurrentMutable.RegisterConstant(hook); + try + { + using var binding = fixture.OneWayBind( + viewModel, + view, + vm => vm.Name, + v => v.NameText, + x => x?.ToUpper() ?? string.Empty); - signal?.Dispose(); + // Binding should be created but produce no values + viewModel.Name = "Changed"; + await Assert.That(view.NameText).IsNotEqualTo("CHANGED"); + } + finally + { + Splat.Locator.CurrentMutable.UnregisterAll(); + foreach (var previousHook in previousHooks) + { + Splat.Locator.CurrentMutable.RegisterConstant(previousHook); + } + } } - private class TestViewModel : ReactiveObject + private class FailingConverter : IBindingTypeConverter { - private string? _name; - private int _count; + public Type FromType => typeof(object); - public string? Name + public Type ToType => typeof(object); + + public int GetAffinityForObjects() => 100; + + public bool TryConvertTyped(object? from, object? conversionHint, out object? result) { - get => _name; - set => this.RaiseAndSetIfChanged(ref _name, value); + result = null; + return false; // Always fails conversion } + } - public int Count + private class NullReturningConverter : IBindingTypeConverter + { + public Type FromType => typeof(object); + + public Type ToType => typeof(object); + + public int GetAffinityForObjects() => 100; + + public bool TryConvertTyped(object? from, object? conversionHint, out object? result) { - get => _count; - set => this.RaiseAndSetIfChanged(ref _count, value); + result = null; + return true; // Returns true but sets result to null } } + private class RejectingBindingHook : IPropertyBindingHook + { + public bool ExecuteHook( + object? source, + object target, + Func[]>? getCurrentViewModelProperties, + Func[]>? getCurrentViewProperties, + BindingDirection direction) => + false; // Always rejects bindings + } + private class TestView : ReactiveObject, IViewFor { - private TestViewModel? _viewModel; - private string? _nameText; - private string? _countText; private int _count; + private string? _countText; + private string? _nameText; + private TestViewModel? _viewModel; - object? IViewFor.ViewModel + public int Count { - get => ViewModel; - set => ViewModel = (TestViewModel?)value; + get => _count; + set => this.RaiseAndSetIfChanged(ref _count, value); } - public TestViewModel? ViewModel + public string? CountText { - get => _viewModel; - set => this.RaiseAndSetIfChanged(ref _viewModel, value); + get => _countText; + set => this.RaiseAndSetIfChanged(ref _countText, value); } public string? NameText @@ -318,51 +340,34 @@ public string? NameText set => this.RaiseAndSetIfChanged(ref _nameText, value); } - public string? CountText - { - get => _countText; - set => this.RaiseAndSetIfChanged(ref _countText, value); - } - - public int Count + public TestViewModel? ViewModel { - get => _count; - set => this.RaiseAndSetIfChanged(ref _count, value); + get => _viewModel; + set => this.RaiseAndSetIfChanged(ref _viewModel, value); } - } - private class NullReturningConverter : IBindingTypeConverter - { - public int GetAffinityForObjects(Type fromType, Type toType) => 100; - - public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result) + object? IViewFor.ViewModel { - result = null; - return true; // Returns true but sets result to null + get => ViewModel; + set => ViewModel = (TestViewModel?)value; } } - private class FailingConverter : IBindingTypeConverter + private class TestViewModel : ReactiveObject { - public int GetAffinityForObjects(Type fromType, Type toType) => 100; + private int _count; + private string? _name; - public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result) + public int Count { - result = null; - return false; // Always fails conversion + get => _count; + set => this.RaiseAndSetIfChanged(ref _count, value); } - } - private class RejectingBindingHook : IPropertyBindingHook - { - public bool ExecuteHook( - object? source, - object target, - Func[]>? getCurrentViewModelProperties, - Func[]>? getCurrentViewProperties, - BindingDirection direction) + public string? Name { - return false; // Always rejects bindings + get => _name; + set => this.RaiseAndSetIfChanged(ref _name, value); } } } diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/OaphNameOfTestFixture.cs b/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/OaphNameOfTestFixture.cs deleted file mode 100644 index aadd6e9613..0000000000 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/OaphNameOfTestFixture.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2025 .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.Runtime.Serialization; -using System.Text.Json.Serialization; - -namespace ReactiveUI.Tests; - -/// -/// A fixture for the OAPH nameof override. -/// -public class OaphNameOfTestFixture : TestFixture -{ - [IgnoreDataMember] - [JsonIgnore] - private readonly ObservableAsPropertyHelper _firstThreeLettersOfOneWord; - - [IgnoreDataMember] - [JsonIgnore] - private readonly ObservableAsPropertyHelper _lastThreeLettersOfOneWord; - - /// - /// Initializes a new instance of the class. - /// - public OaphNameOfTestFixture() - { - this.WhenAnyValue(static x => x.IsOnlyOneWord).Select(static x => x ?? string.Empty).Select(static x => x.Length >= 3 ? x.Substring(0, 3) : x).ToProperty(this, nameof(FirstThreeLettersOfOneWord), out _firstThreeLettersOfOneWord); - _lastThreeLettersOfOneWord = this.WhenAnyValue(static x => x.IsOnlyOneWord).Select(static x => x ?? string.Empty).Select(static x => x.Length >= 3 ? x.Substring(x.Length - 3, 3) : x).ToProperty(this, nameof(LastThreeLettersOfOneWord)); - } - - /// - /// Gets the first three letters of one word. - /// - [IgnoreDataMember] - [JsonIgnore] - public string? FirstThreeLettersOfOneWord => _firstThreeLettersOfOneWord.Value; - - /// - /// Gets the last three letters of one word. - /// - [IgnoreDataMember] - [JsonIgnore] - public string LastThreeLettersOfOneWord => _lastThreeLettersOfOneWord.Value; -} diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/OaphTestFixture.cs b/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/OaphTestFixture.cs deleted file mode 100644 index 5c03e5187a..0000000000 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/OaphTestFixture.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2025 .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.Runtime.Serialization; -using System.Text.Json.Serialization; - -namespace ReactiveUI.Tests; - -/// -/// Initializes a new instance of the class. -/// -/// -public class OaphTestFixture : TestFixture - { - [IgnoreDataMember] - [JsonIgnore] - private readonly ObservableAsPropertyHelper _firstThreeLettersOfOneWord; - - /// - /// Initializes a new instance of the class. - /// - public OaphTestFixture() => this.WhenAnyValue(static x => x.IsOnlyOneWord).Select(static x => x ?? string.Empty).Select(static x => x.Length >= 3 ? x.Substring(0, 3) : x).ToProperty(this, static x => x.FirstThreeLettersOfOneWord, out _firstThreeLettersOfOneWord); - - /// - /// Gets the first three letters of one word. - /// - [IgnoreDataMember] - [JsonIgnore] - public string? FirstThreeLettersOfOneWord => _firstThreeLettersOfOneWord.Value; - } diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/ProjectService.cs b/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/ProjectService.cs deleted file mode 100644 index 0b7d9c2872..0000000000 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/ProjectService.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2025 .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; - -/// -/// Project Service. -/// -/// -public class ProjectService : ReactiveObject -{ - /// - /// Initializes a new instance of the class. - /// - public ProjectService() - { - Projects.Add(Guid.NewGuid(), new() { Name = "Dummy1" }); - Projects.Add(Guid.NewGuid(), new() { Name = "Dummy2" }); - Projects.Add(Guid.NewGuid(), new() { Name = "Dummy3" }); - Projects.Add(Guid.NewGuid(), new() { Name = "Dummy4" }); - Projects.Add(Guid.NewGuid(), new() { Name = "Dummy5" }); - - ProjectsNullable.Add(Guid.NewGuid(), new() { Name = "Dummy1" }); - ProjectsNullable.Add(Guid.NewGuid(), new() { Name = "Dummy2" }); - ProjectsNullable.Add(Guid.NewGuid(), new() { Name = "Dummy3" }); - ProjectsNullable.Add(Guid.NewGuid(), new() { Name = "Dummy4" }); - ProjectsNullable.Add(Guid.NewGuid(), null); - } - - /// - /// Gets the projects. - /// - /// - /// The projects. - /// - public Dictionary Projects { get; } = []; - - /// - /// Gets the projects nullable. - /// - /// - /// The projects nullable. - /// - public Dictionary ProjectsNullable { get; } = []; -} diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/TestFixture.cs b/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/TestFixture.cs deleted file mode 100644 index 745db648c2..0000000000 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/TestFixture.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) 2025 .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.Runtime.Serialization; -using System.Text.Json.Serialization; - -using DynamicData.Binding; - -namespace ReactiveUI.Tests; - -/// -/// A test fixture. -/// -/// -[DataContract] -public class TestFixture : ReactiveObject -{ - [IgnoreDataMember] - [JsonIgnore] - private string? _isNotNullString; - - [IgnoreDataMember] - [JsonIgnore] - private string? _isOnlyOneWord; - - private string? _notSerialized; - - [IgnoreDataMember] - [JsonIgnore] - private int? _nullableInt; - - [IgnoreDataMember] - [JsonIgnore] - private List? _stackOverflowTrigger; - - [IgnoreDataMember] - [JsonIgnore] - private string? _usesExprRaiseSet; - - /// - /// Initializes a new instance of the class. - /// - public TestFixture() => TestCollection = []; - - /// - /// Gets or sets the is not null string. - /// - [DataMember] - [JsonRequired] - public string? IsNotNullString - { - get => _isNotNullString; - set => this.RaiseAndSetIfChanged(ref _isNotNullString, value); - } - - /// - /// Gets or sets the is only one word. - /// - [DataMember] - [JsonRequired] - public string? IsOnlyOneWord - { - get => _isOnlyOneWord; - set => this.RaiseAndSetIfChanged(ref _isOnlyOneWord, value); - } - - /// - /// Gets or sets the not serialized. - /// - public string? NotSerialized - { - get => _notSerialized; - set => this.RaiseAndSetIfChanged(ref _notSerialized, value); - } - - /// - /// Gets or sets the nullable int. - /// - [DataMember] - [JsonRequired] - public int? NullableInt - { - get => _nullableInt; - set => this.RaiseAndSetIfChanged(ref _nullableInt, value); - } - - /// - /// Gets or sets the poco property. - /// - [DataMember] - [field: IgnoreDataMember] - [JsonRequired] - public string? PocoProperty { get; set; } - - /// - /// Gets or sets the stack overflow trigger. - /// - [DataMember] - [JsonRequired] - public List? StackOverflowTrigger - { - get => _stackOverflowTrigger; - set => this.RaiseAndSetIfChanged(ref _stackOverflowTrigger, value?.ToList()); - } - - /// - /// Gets or sets the test collection. - /// - [DataMember] - [JsonRequired] - public ObservableCollectionExtended TestCollection { get; set; } - - /// - /// Gets or sets the uses expr raise set. - /// - [DataMember] - [JsonRequired] - public string? UsesExprRaiseSet - { - get => _usesExprRaiseSet; - set => this.RaiseAndSetIfChanged(ref _usesExprRaiseSet, value); - } -} diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/AccountService.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/AccountService.cs similarity index 50% rename from src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/AccountService.cs rename to src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/AccountService.cs index 1f3cd99a5f..258a3035a3 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/AccountService.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/AccountService.cs @@ -3,42 +3,42 @@ // 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; +namespace ReactiveUI.Tests.ReactiveObjects.Mocks; /// -/// Account Service. +/// Account Service. /// /// public class AccountService : ReactiveObject { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public AccountService() { - AccountUsers.Add(Guid.NewGuid(), new() { LastName = "Harris" }); - AccountUsers.Add(Guid.NewGuid(), new() { LastName = "Jones" }); - AccountUsers.Add(Guid.NewGuid(), new() { LastName = "Smith" }); + AccountUsers.Add(Guid.NewGuid(), new AccountUser { LastName = "Harris" }); + AccountUsers.Add(Guid.NewGuid(), new AccountUser { LastName = "Jones" }); + AccountUsers.Add(Guid.NewGuid(), new AccountUser { LastName = "Smith" }); - AccountUsersNullable.Add(Guid.NewGuid(), new() { LastName = "Harris" }); - AccountUsersNullable.Add(Guid.NewGuid(), new() { LastName = "Jones" }); - AccountUsersNullable.Add(Guid.NewGuid(), new() { LastName = "Smith" }); + AccountUsersNullable.Add(Guid.NewGuid(), new AccountUser { LastName = "Harris" }); + AccountUsersNullable.Add(Guid.NewGuid(), new AccountUser { LastName = "Jones" }); + AccountUsersNullable.Add(Guid.NewGuid(), new AccountUser { LastName = "Smith" }); AccountUsersNullable.Add(Guid.NewGuid(), null); } /// - /// Gets the account users. + /// Gets the account users. /// /// - /// The account users. + /// The account users. /// public Dictionary AccountUsers { get; } = []; /// - /// Gets the account users nullable. + /// Gets the account users nullable. /// /// - /// The account users nullable. + /// The account users nullable. /// public Dictionary AccountUsersNullable { get; } = []; } diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/AccountUser.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/AccountUser.cs similarity index 84% rename from src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/AccountUser.cs rename to src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/AccountUser.cs index 54e10802db..8b4c7b72af 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/AccountUser.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/AccountUser.cs @@ -6,10 +6,10 @@ using System.Runtime.Serialization; using System.Text.Json.Serialization; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.ReactiveObjects.Mocks; /// -/// Account User. +/// Account User. /// /// [DataContract] @@ -18,10 +18,10 @@ public class AccountUser : ReactiveObject private string? _lastName; /// - /// Gets or sets the last name. + /// Gets or sets the last name. /// /// - /// The last name. + /// The last name. /// [DataMember] [JsonRequired] diff --git a/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphNameOfTestFixture.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphNameOfTestFixture.cs new file mode 100644 index 0000000000..c341c43b24 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphNameOfTestFixture.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 .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.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace ReactiveUI.Tests.ReactiveObjects.Mocks; + +/// +/// A fixture for the OAPH nameof override. +/// +public class OaphNameOfTestFixture : TestFixture +{ + [IgnoreDataMember] + [JsonIgnore] + private readonly ObservableAsPropertyHelper _firstThreeLettersOfOneWord; + + [IgnoreDataMember] + [JsonIgnore] + private readonly ObservableAsPropertyHelper _lastThreeLettersOfOneWord; + + /// + /// Initializes a new instance of the class. + /// + public OaphNameOfTestFixture() + { + this.WhenAnyValue(static x => x.IsOnlyOneWord).Select(static x => x ?? string.Empty) + .Select(static x => x.Length >= 3 ? x.Substring(0, 3) : x).ToProperty( + this, + nameof(FirstThreeLettersOfOneWord), + out _firstThreeLettersOfOneWord); + _lastThreeLettersOfOneWord = this.WhenAnyValue(static x => x.IsOnlyOneWord) + .Select(static x => x ?? string.Empty).Select(static x => x.Length >= 3 ? x.Substring(x.Length - 3, 3) : x) + .ToProperty(this, nameof(LastThreeLettersOfOneWord)); + } + + /// + /// Gets the first three letters of one word. + /// + [IgnoreDataMember] + [JsonIgnore] + public string? FirstThreeLettersOfOneWord => _firstThreeLettersOfOneWord.Value; + + /// + /// Gets the last three letters of one word. + /// + [IgnoreDataMember] + [JsonIgnore] + public string LastThreeLettersOfOneWord => _lastThreeLettersOfOneWord.Value; +} diff --git a/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphTestFixture.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphTestFixture.cs new file mode 100644 index 0000000000..b65e6bf66e --- /dev/null +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphTestFixture.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2025 .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.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace ReactiveUI.Tests.ReactiveObjects.Mocks; + +/// +/// Initializes a new instance of the class. +/// +/// +public class OaphTestFixture : TestFixture +{ + [IgnoreDataMember] + [JsonIgnore] + private readonly ObservableAsPropertyHelper _firstThreeLettersOfOneWord; + + /// + /// Initializes a new instance of the class. + /// + public OaphTestFixture() => this.WhenAnyValue(static x => x.IsOnlyOneWord).Select(static x => x ?? string.Empty) + .Select(static x => x.Length >= 3 ? x.Substring(0, 3) : x).ToProperty( + this, + static x => x.FirstThreeLettersOfOneWord, + out _firstThreeLettersOfOneWord); + + /// + /// Gets the first three letters of one word. + /// + [IgnoreDataMember] + [JsonIgnore] + public string? FirstThreeLettersOfOneWord => _firstThreeLettersOfOneWord.Value; +} diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/Project.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/Project.cs similarity index 56% rename from src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/Project.cs rename to src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/Project.cs index 154a5645ba..178f705130 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/Project.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/Project.cs @@ -6,28 +6,28 @@ using System.Runtime.Serialization; using System.Text.Json.Serialization; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.ReactiveObjects.Mocks; /// -/// Project. +/// Project. /// /// [DataContract] public class Project : ReactiveObject { - private string? _name; + private string? _name; - /// - /// Gets or sets the name. - /// - /// - /// The name. - /// - [DataMember] - [JsonRequired] - public string? Name - { - get => _name; - set => this.RaiseAndSetIfChanged(ref _name, value); - } + /// + /// Gets or sets the name. + /// + /// + /// The name. + /// + [DataMember] + [JsonRequired] + public string? Name + { + get => _name; + set => this.RaiseAndSetIfChanged(ref _name, value); + } } diff --git a/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/ProjectService.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/ProjectService.cs new file mode 100644 index 0000000000..863ff97d0d --- /dev/null +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/ProjectService.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2025 .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.ReactiveObjects.Mocks; + +/// +/// Project Service. +/// +/// +public class ProjectService : ReactiveObject +{ + /// + /// Initializes a new instance of the class. + /// + public ProjectService() + { + Projects.Add(Guid.NewGuid(), new Project { Name = "Dummy1" }); + Projects.Add(Guid.NewGuid(), new Project { Name = "Dummy2" }); + Projects.Add(Guid.NewGuid(), new Project { Name = "Dummy3" }); + Projects.Add(Guid.NewGuid(), new Project { Name = "Dummy4" }); + Projects.Add(Guid.NewGuid(), new Project { Name = "Dummy5" }); + + ProjectsNullable.Add(Guid.NewGuid(), new Project { Name = "Dummy1" }); + ProjectsNullable.Add(Guid.NewGuid(), new Project { Name = "Dummy2" }); + ProjectsNullable.Add(Guid.NewGuid(), new Project { Name = "Dummy3" }); + ProjectsNullable.Add(Guid.NewGuid(), new Project { Name = "Dummy4" }); + ProjectsNullable.Add(Guid.NewGuid(), null); + } + + /// + /// Gets the projects. + /// + /// + /// The projects. + /// + public Dictionary Projects { get; } = []; + + /// + /// Gets the projects nullable. + /// + /// + /// The projects nullable. + /// + public Dictionary ProjectsNullable { get; } = []; +} diff --git a/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/TestFixture.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/TestFixture.cs new file mode 100644 index 0000000000..3fee48af7c --- /dev/null +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/TestFixture.cs @@ -0,0 +1,123 @@ +// Copyright (c) 2025 .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.Runtime.Serialization; +using System.Text.Json.Serialization; +using DynamicData.Binding; + +namespace ReactiveUI.Tests.ReactiveObjects.Mocks; + +/// +/// A test fixture. +/// +/// +[DataContract] +public class TestFixture : ReactiveObject +{ + [IgnoreDataMember] + [JsonIgnore] + private string? _isNotNullString; + + [IgnoreDataMember] + [JsonIgnore] + private string? _isOnlyOneWord; + + private string? _notSerialized; + + [IgnoreDataMember] + [JsonIgnore] + private int? _nullableInt; + + [IgnoreDataMember] + [JsonIgnore] + private List? _stackOverflowTrigger; + + [IgnoreDataMember] + [JsonIgnore] + private string? _usesExprRaiseSet; + + /// + /// Initializes a new instance of the class. + /// + public TestFixture() => TestCollection = []; + + /// + /// Gets or sets the is not null string. + /// + [DataMember] + [JsonRequired] + public string? IsNotNullString + { + get => _isNotNullString; + set => this.RaiseAndSetIfChanged(ref _isNotNullString, value); + } + + /// + /// Gets or sets the is only one word. + /// + [DataMember] + [JsonRequired] + public string? IsOnlyOneWord + { + get => _isOnlyOneWord; + set => this.RaiseAndSetIfChanged(ref _isOnlyOneWord, value); + } + + /// + /// Gets or sets the not serialized. + /// + public string? NotSerialized + { + get => _notSerialized; + set => this.RaiseAndSetIfChanged(ref _notSerialized, value); + } + + /// + /// Gets or sets the nullable int. + /// + [DataMember] + [JsonRequired] + public int? NullableInt + { + get => _nullableInt; + set => this.RaiseAndSetIfChanged(ref _nullableInt, value); + } + + /// + /// Gets or sets the poco property. + /// + [field: IgnoreDataMember] + [JsonIgnore] + public string? PocoProperty { get; set; } + + /// + /// Gets or sets the stack overflow trigger. + /// + [DataMember] + [JsonRequired] + public List? StackOverflowTrigger + { + get => _stackOverflowTrigger; + set => this.RaiseAndSetIfChanged(ref _stackOverflowTrigger, value?.ToList()); + } + + /// + /// Gets or sets the test collection. + /// + [DataMember] + [JsonRequired] + public ObservableCollectionExtended TestCollection { get; set; } + + /// + /// Gets or sets the uses expr raise set. + /// + [DataMember] + [JsonRequired] + public string? UsesExprRaiseSet + { + get => _usesExprRaiseSet; + set => this.RaiseAndSetIfChanged(ref _usesExprRaiseSet, value); + } +} diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/WhenAnyTestFixture.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/WhenAnyTestFixture.cs similarity index 78% rename from src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/WhenAnyTestFixture.cs rename to src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/WhenAnyTestFixture.cs index 57616756f3..69ce5d8faa 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/Mocks/WhenAnyTestFixture.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/WhenAnyTestFixture.cs @@ -6,15 +6,11 @@ using System.Runtime.Serialization; using System.Text.Json.Serialization; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.ReactiveObjects.Mocks; [DataContract] public class WhenAnyTestFixture : ReactiveObject { - [IgnoreDataMember] - [JsonIgnore] - private ObservableAsPropertyHelper? _accountsFoundHelper; - [IgnoreDataMember] [JsonIgnore] private AccountService _accountService = new(); @@ -24,6 +20,9 @@ public class WhenAnyTestFixture : ReactiveObject private ProjectService _projectService = new(); private string _value1 = "1"; + private string? _value10; + private string _value11 = "11"; + private string? _value12; private string? _value2; private string _value3 = "3"; private string? _value4; @@ -32,15 +31,19 @@ public class WhenAnyTestFixture : ReactiveObject private string _value7 = "7"; private string? _value8; private string _value9 = "9"; - private string? _value10; - private string _value11 = "11"; - private string? _value12; /// - /// Gets or sets the account service. + /// Gets the first three letters of one word. + /// + [IgnoreDataMember] + [JsonIgnore] + public int AccountsFound => AccountsFoundHelper!.Value; + + /// + /// Gets or sets the account service. /// /// - /// The account service. + /// The account service. /// [DataMember] [JsonRequired] @@ -50,11 +53,15 @@ public AccountService AccountService set => this.RaiseAndSetIfChanged(ref _accountService, value); } + [IgnoreDataMember] + [JsonIgnore] + public ObservableAsPropertyHelper? AccountsFoundHelper { get; set; } + /// - /// Gets or sets the project service. + /// Gets or sets the project service. /// /// - /// The project service. + /// The project service. /// [DataMember] [JsonRequired] @@ -65,25 +72,10 @@ public ProjectService ProjectService } /// - /// Gets the first three letters of one word. - /// - [IgnoreDataMember] - [JsonIgnore] - public int AccountsFound => _accountsFoundHelper!.Value; - - [IgnoreDataMember] - [JsonIgnore] - public ObservableAsPropertyHelper? AccountsFoundHelper - { - get => _accountsFoundHelper; - set => _accountsFoundHelper = value; - } - - /// - /// Gets or sets the value1. + /// Gets or sets the value1. /// /// - /// The value1. + /// The value1. /// [DataMember] [JsonRequired] @@ -94,156 +86,156 @@ public string Value1 } /// - /// Gets or sets the value2. + /// Gets or sets the value10. /// /// - /// The value2. + /// The value10. /// [DataMember] [JsonRequired] - public string? Value2 + public string? Value10 { - get => _value2; - set => this.RaiseAndSetIfChanged(ref _value2, value); + get => _value10; + set => this.RaiseAndSetIfChanged(ref _value10, value); } /// - /// Gets or sets the value3. + /// Gets or sets the value11. /// /// - /// The value3. + /// The value11. /// [DataMember] [JsonRequired] - public string Value3 + public string Value11 { - get => _value3; - set => this.RaiseAndSetIfChanged(ref _value3, value); + get => _value11; + set => this.RaiseAndSetIfChanged(ref _value11, value); } /// - /// Gets or sets the value4. + /// Gets or sets the value12. /// /// - /// The value4. + /// The value12. /// [DataMember] [JsonRequired] - public string? Value4 + public string? Value12 { - get => _value4; - set => this.RaiseAndSetIfChanged(ref _value4, value); + get => _value12; + set => this.RaiseAndSetIfChanged(ref _value12, value); } /// - /// Gets or sets the value5. + /// Gets or sets the value2. /// /// - /// The value5. + /// The value2. /// [DataMember] [JsonRequired] - public string Value5 + public string? Value2 { - get => _value5; - set => this.RaiseAndSetIfChanged(ref _value5, value); + get => _value2; + set => this.RaiseAndSetIfChanged(ref _value2, value); } /// - /// Gets or sets the value6. + /// Gets or sets the value3. /// /// - /// The value6. + /// The value3. /// [DataMember] [JsonRequired] - public string? Value6 + public string Value3 { - get => _value6; - set => this.RaiseAndSetIfChanged(ref _value6, value); + get => _value3; + set => this.RaiseAndSetIfChanged(ref _value3, value); } /// - /// Gets or sets the value7. + /// Gets or sets the value4. /// /// - /// The value7. + /// The value4. /// [DataMember] [JsonRequired] - public string Value7 + public string? Value4 { - get => _value7; - set => this.RaiseAndSetIfChanged(ref _value7, value); + get => _value4; + set => this.RaiseAndSetIfChanged(ref _value4, value); } /// - /// Gets or sets the value8. + /// Gets or sets the value5. /// /// - /// The value8. + /// The value5. /// [DataMember] [JsonRequired] - public string? Value8 + public string Value5 { - get => _value8; - set => this.RaiseAndSetIfChanged(ref _value8, value); + get => _value5; + set => this.RaiseAndSetIfChanged(ref _value5, value); } /// - /// Gets or sets the value9. + /// Gets or sets the value6. /// /// - /// The value9. + /// The value6. /// [DataMember] [JsonRequired] - public string Value9 + public string? Value6 { - get => _value9; - set => this.RaiseAndSetIfChanged(ref _value9, value); + get => _value6; + set => this.RaiseAndSetIfChanged(ref _value6, value); } /// - /// Gets or sets the value10. + /// Gets or sets the value7. /// /// - /// The value10. + /// The value7. /// [DataMember] [JsonRequired] - public string? Value10 + public string Value7 { - get => _value10; - set => this.RaiseAndSetIfChanged(ref _value10, value); + get => _value7; + set => this.RaiseAndSetIfChanged(ref _value7, value); } /// - /// Gets or sets the value11. + /// Gets or sets the value8. /// /// - /// The value11. + /// The value8. /// [DataMember] [JsonRequired] - public string Value11 + public string? Value8 { - get => _value11; - set => this.RaiseAndSetIfChanged(ref _value11, value); + get => _value8; + set => this.RaiseAndSetIfChanged(ref _value8, value); } /// - /// Gets or sets the value12. + /// Gets or sets the value9. /// /// - /// The value12. + /// The value9. /// [DataMember] [JsonRequired] - public string? Value12 + public string Value9 { - get => _value12; - set => this.RaiseAndSetIfChanged(ref _value12, value); + get => _value9; + set => this.RaiseAndSetIfChanged(ref _value9, value); } } diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/ReactiveObjectTests.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveObjectTests.cs similarity index 66% rename from src/tests/ReactiveUI.Tests/ReactiveObject/ReactiveObjectTests.cs rename to src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveObjectTests.cs index a2bbb5a346..0d2b12c258 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/ReactiveObjectTests.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveObjectTests.cs @@ -5,57 +5,53 @@ using System.Collections; using System.Text.Json; - using DynamicData; +using ReactiveUI.Tests.ReactiveObjects.Mocks; +using ReactiveUI.Tests.Utilities; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.ReactiveObjects; public class ReactiveObjectTests { /// - /// Test that changing values should always arrive before changed. + /// Test that changing values should always arrive before changed. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ChangingShouldAlwaysArriveBeforeChanged() { const string beforeSet = "Foo"; const string afterSet = "Bar"; - var fixture = new TestFixture - { - IsOnlyOneWord = beforeSet - }; + var fixture = new TestFixture { IsOnlyOneWord = beforeSet }; var beforeFired = false; - fixture.Changing.Subscribe( - async x => - { - using (Assert.Multiple()) - { - // XXX: The content of these asserts don't actually get - // propagated back, it only prevents before_fired from - // being set - we have to enable 1st-chance exceptions - // to see the real error - await Assert.That(x.PropertyName).IsEqualTo("IsOnlyOneWord"); - await Assert.That(fixture.IsOnlyOneWord).IsEqualTo(beforeSet); - } - - beforeFired = true; - }); + fixture.Changing.Subscribe(async x => + { + using (Assert.Multiple()) + { + // XXX: The content of these asserts don't actually get + // propagated back, it only prevents before_fired from + // being set - we have to enable 1st-chance exceptions + // to see the real error + await Assert.That(x.PropertyName).IsEqualTo("IsOnlyOneWord"); + await Assert.That(fixture.IsOnlyOneWord).IsEqualTo(beforeSet); + } + + beforeFired = true; + }); var afterFired = false; - fixture.Changed.Subscribe( - async x => - { - using (Assert.Multiple()) - { - await Assert.That(x.PropertyName).IsEqualTo("IsOnlyOneWord"); - await Assert.That(fixture.IsOnlyOneWord).IsEqualTo(afterSet); - } - - afterFired = true; - }); + fixture.Changed.Subscribe(async x => + { + using (Assert.Multiple()) + { + await Assert.That(x.PropertyName).IsEqualTo("IsOnlyOneWord"); + await Assert.That(fixture.IsOnlyOneWord).IsEqualTo(afterSet); + } + + afterFired = true; + }); fixture.IsOnlyOneWord = afterSet; @@ -67,9 +63,9 @@ public async Task ChangingShouldAlwaysArriveBeforeChanged() } /// - /// Test that deferring the notifications dont show up until undeferred. + /// Test that deferring the notifications dont show up until undeferred. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task DeferredNotificationsDontShowUpUntilUndeferred() { @@ -121,46 +117,42 @@ public async Task DeferredNotificationsDontShowUpUntilUndeferred() { await Assert.That(changing.Select(e => e.PropertyName!)).IsEquivalentTo(expectedEventProperties); await Assert.That(changed.Select(e => e.PropertyName!)).IsEquivalentTo(expectedEventProperties); - await Assert.That(propertyChangingEvents.Select(e => e.PropertyName!)).IsEquivalentTo(expectedEventProperties); - await Assert.That(propertyChangedEvents.Select(e => e.PropertyName!)).IsEquivalentTo(expectedEventProperties); + await Assert.That(propertyChangingEvents.Select(e => e.PropertyName!)) + .IsEquivalentTo(expectedEventProperties); + await Assert.That(propertyChangedEvents.Select(e => e.PropertyName!)) + .IsEquivalentTo(expectedEventProperties); } } /// - /// Test that exceptions thrown in subscribers should marshal to thrown exceptions. + /// Test that exceptions thrown in subscribers should marshal to thrown exceptions. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ExceptionsThrownInSubscribersShouldMarshalToThrownExceptions() { - var fixture = new TestFixture - { - IsOnlyOneWord = "Foo" - }; + var fixture = new TestFixture { IsOnlyOneWord = "Foo" }; fixture.Changed.Subscribe(static _ => throw new Exception("Die!")); - fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var exceptionList).Subscribe(); + fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var exceptionList) + .Subscribe(); fixture.IsOnlyOneWord = "Bar"; await Assert.That(exceptionList).Count().IsEqualTo(1); } /// - /// Tests that ObservableForProperty using expression. + /// Tests that ObservableForProperty using expression. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ObservableForPropertyUsingExpression() { - var fixture = new TestFixture - { - IsNotNullString = "Foo", - IsOnlyOneWord = "Baz" - }; + var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz" }; var output = new List>(); fixture.ObservableForProperty(x => x.IsNotNullString) - .WhereNotNull() - .Subscribe(x => output.Add(x)); + .WhereNotNull() + .Subscribe(x => output.Add(x)); fixture.IsNotNullString = "Bar"; fixture.IsNotNullString = "Baz"; @@ -183,22 +175,18 @@ public async Task ObservableForPropertyUsingExpression() } /// - /// Test raises and set using expression. + /// Test raises and set using expression. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task RaiseAndSetUsingExpression() { - var fixture = new TestFixture - { - IsNotNullString = "Foo", - IsOnlyOneWord = "Baz" - }; + var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz" }; var output = new List(); fixture.Changed - .Where(x => x.PropertyName is not null) - .Select(x => x.PropertyName!) - .Subscribe(x => output.Add(x)); + .Where(x => x.PropertyName is not null) + .Select(x => x.PropertyName!) + .Subscribe(x => output.Add(x)); fixture.UsesExprRaiseSet = "Foo"; fixture.UsesExprRaiseSet = "Foo"; // This one shouldn't raise a change notification @@ -212,34 +200,69 @@ public async Task RaiseAndSetUsingExpression() await Assert.That(output[0]).IsEqualTo("UsesExprRaiseSet"); } + [Test] + public async Task ReactiveObjectCanSuppressChangeNotifications() + { + var fixture = new TestFixture(); + using (fixture.SuppressChangeNotifications()) + { + await Assert.That(fixture.AreChangeNotificationsEnabled()).IsFalse(); + } + + await Assert.That(fixture.AreChangeNotificationsEnabled()).IsTrue(); + + var ser = JsonSerializer.Serialize(fixture); + await Assert.That(ser).IsNotEmpty(); + var deser = JsonSerializer.Deserialize(ser); + await Assert.That(deser).IsNotNull(); + + using (deser.SuppressChangeNotifications()) + { + await Assert.That(deser!.AreChangeNotificationsEnabled()).IsFalse(); + } + + await Assert.That(deser!.AreChangeNotificationsEnabled()).IsTrue(); + } + /// - /// Test that ReactiveObject shouldn't serialize anything extra. + /// Test that ReactiveObject shouldn't serialize anything extra. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ReactiveObjectShouldntSerializeAnythingExtra() { - var fixture = new TestFixture - { - IsNotNullString = "Foo", - IsOnlyOneWord = "Baz" - }; - var json = JSONHelper.Serialize(fixture) ?? throw new InvalidOperationException("JSON string should not be null"); + var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz" }; + var json = JSONHelper.Serialize(fixture) ?? + throw new InvalidOperationException("JSON string should not be null"); using (Assert.Multiple()) { // Should look something like: - // {"IsNotNullString":"Foo","IsOnlyOneWord":"Baz","NullableInt":null,"PocoProperty":null,"StackOverflowTrigger":null,"TestCollection":[],"UsesExprRaiseSet":null} - await Assert.That(json.Count(static x => x == ',')).IsEqualTo(6); - await Assert.That(json.Count(static x => x == ':')).IsEqualTo(7); - await Assert.That(json.Count(static x => x == '"')).IsEqualTo(18); + // {"IsNotNullString":"Foo","IsOnlyOneWord":"Baz","NullableInt":null,"StackOverflowTrigger":null,"TestCollection":[],"UsesExprRaiseSet":null} + // PocoProperty is excluded because it lacks [DataMember] attribute + await Assert.That(json.Count(static x => x == ',')).IsEqualTo(5); + await Assert.That(json.Count(static x => x == ':')).IsEqualTo(6); + await Assert.That(json.Count(static x => x == '"')).IsEqualTo(16); } } /// - /// Performs a ReactiveObject smoke test. + /// Tests to make sure that ReactiveObject doesn't rethrow exceptions. /// - /// A representing the asynchronous operation. + [Test] + public void ReactiveObjectShouldRethrowException() + { + var fixture = new TestFixture(); + var observable = fixture.WhenAnyValue(x => x.IsOnlyOneWord).Skip(1); + observable.Subscribe(_ => throw new Exception("This is a test.")); + + Assert.Throws(() => fixture.IsOnlyOneWord = "Two Words"); + } + + /// + /// Performs a ReactiveObject smoke test. + /// + /// A representing the asynchronous operation. [Test] public async Task ReactiveObjectSmokeTest() { @@ -248,13 +271,13 @@ public async Task ReactiveObjectSmokeTest() var fixture = new TestFixture(); fixture.Changing - .Where(x => x.PropertyName is not null) - .Select(x => x.PropertyName!) - .Subscribe(x => outputChanging.Add(x)); + .Where(x => x.PropertyName is not null) + .Select(x => x.PropertyName!) + .Subscribe(x => outputChanging.Add(x)); fixture.Changed - .Where(x => x.PropertyName is not null) - .Select(x => x.PropertyName!) - .Subscribe(x => output.Add(x)); + .Where(x => x.PropertyName is not null) + .Select(x => x.PropertyName!) + .Subscribe(x => output.Add(x)); fixture.IsNotNullString = "Foo Bar Baz"; fixture.IsOnlyOneWord = "Foo"; @@ -270,43 +293,6 @@ public async Task ReactiveObjectSmokeTest() await results.AssertAreEqual(output); } - /// - /// Tests to make sure that ReactiveObject doesn't rethrow exceptions. - /// - [Test] - public void ReactiveObjectShouldRethrowException() - { - var fixture = new TestFixture(); - var observable = fixture.WhenAnyValue(x => x.IsOnlyOneWord).Skip(1); - observable.Subscribe(_ => throw new Exception("This is a test.")); - - Assert.Throws(() => fixture.IsOnlyOneWord = "Two Words"); - } - - [Test] - public async Task ReactiveObjectCanSuppressChangeNotifications() - { - var fixture = new TestFixture(); - using (fixture.SuppressChangeNotifications()) - { - await Assert.That(fixture.AreChangeNotificationsEnabled()).IsFalse(); - } - - await Assert.That(fixture.AreChangeNotificationsEnabled()).IsTrue(); - - var ser = JsonSerializer.Serialize(fixture); - await Assert.That(ser).IsNotEmpty(); - var deser = JsonSerializer.Deserialize(ser); - await Assert.That(deser).IsNotNull(); - - using (deser.SuppressChangeNotifications()) - { - await Assert.That(deser!.AreChangeNotificationsEnabled()).IsFalse(); - } - - await Assert.That(deser!.AreChangeNotificationsEnabled()).IsTrue(); - } - private static async Task AssertCount(int expected, params ICollection[] collections) { foreach (var collection in collections) diff --git a/src/tests/ReactiveUI.Tests/ReactiveObject/ReactiveRecordTests.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveRecordTests.cs similarity index 72% rename from src/tests/ReactiveUI.Tests/ReactiveObject/ReactiveRecordTests.cs rename to src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveRecordTests.cs index e068f420ca..b0cfec3a2c 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveObject/ReactiveRecordTests.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveRecordTests.cs @@ -7,54 +7,17 @@ using System.Text.Json; using DynamicData; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.ReactiveObjects; /// -/// Tests for ReactiveRecord - a record-based reactive object implementation. +/// Tests for ReactiveRecord - a record-based reactive object implementation. /// public class ReactiveRecordTests { /// - /// Test that ReactiveRecord properties raise changing and changed notifications. + /// Test that the Changing observable fires before property changes. /// - /// A representing the asynchronous operation. - [Test] - public async Task ReactiveRecordShouldRaisePropertyChangeNotifications() - { - var fixture = new TestRecord { Name = "Initial" }; - var changingEvents = new List(); - var changedEvents = new List(); - - using var sub1 = fixture.Changing - .Where(x => x.PropertyName is not null) - .Select(x => x.PropertyName!) - .Subscribe(x => changingEvents.Add(x)); - - using var sub2 = fixture.Changed - .Where(x => x.PropertyName is not null) - .Select(x => x.PropertyName!) - .Subscribe(x => changedEvents.Add(x)); - - var updated = fixture with { Name = "Updated" }; - - // Records create new instances, so we need to subscribe to the new instance - using var sub3 = updated.Changing - .Where(x => x.PropertyName is not null) - .Select(x => x.PropertyName!) - .Subscribe(x => changingEvents.Add(x)); - - using var sub4 = updated.Changed - .Where(x => x.PropertyName is not null) - .Select(x => x.PropertyName!) - .Subscribe(x => changedEvents.Add(x)); - - await Assert.That(updated.Name).IsEqualTo("Updated"); - } - - /// - /// Test that the Changing observable fires before property changes. - /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ChangingObservableShouldFireBeforePropertyChanges() { @@ -76,27 +39,48 @@ public async Task ChangingObservableShouldFireBeforePropertyChanges() } /// - /// Test that ThrownExceptions observable is initialized. + /// Test that DelayChangeNotifications defers notifications until disposed. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ThrownExceptionsObservableShouldBeInitialized() + public async Task DelayChangeNotificationsShouldDeferNotifications() { - var fixture = new TestRecord(); - await Assert.That(fixture.ThrownExceptions).IsNotNull(); + var fixture = new TestMutableRecord(); + using var sub1 = fixture.Changed.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var changed) + .Subscribe(); + using var sub2 = fixture.Changing.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var changing) + .Subscribe(); + + await AssertCount(0, changing, changed); + + fixture.Value = 1; + await AssertCount(1, changing, changed); + + using (fixture.DelayChangeNotifications()) + { + fixture.Value = 2; + await AssertCount(1, changing, changed); + + fixture.Value = 3; + await AssertCount(1, changing, changed); + } + + // After disposing, delayed notifications should fire + await AssertCount(2, changing, changed); } /// - /// Test that exceptions thrown in subscribers are marshaled to ThrownExceptions. + /// Test that exceptions thrown in subscribers are marshaled to ThrownExceptions. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ExceptionsShouldMarshalToThrownExceptions() { var fixture = new TestMutableRecord { Value = 1 }; using var sub1 = fixture.Changed.Subscribe(static _ => throw new Exception("Test exception")); - using var sub2 = fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var exceptions).Subscribe(); + using var sub2 = fixture.ThrownExceptions.ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var exceptions).Subscribe(); fixture.Value = 2; @@ -104,65 +88,47 @@ public async Task ExceptionsShouldMarshalToThrownExceptions() } /// - /// Test that SuppressChangeNotifications works correctly. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SuppressChangeNotificationsShouldWork() - { - var fixture = new TestMutableRecord(); - - await Assert.That(fixture.AreChangeNotificationsEnabled()).IsTrue(); - - using (fixture.SuppressChangeNotifications()) - { - await Assert.That(fixture.AreChangeNotificationsEnabled()).IsFalse(); - } - - await Assert.That(fixture.AreChangeNotificationsEnabled()).IsTrue(); - } - - /// - /// Test that DelayChangeNotifications defers notifications until disposed. + /// Test that nested change notifications work correctly. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task DelayChangeNotificationsShouldDeferNotifications() + public async Task NestedDelayChangeNotificationsShouldWork() { var fixture = new TestMutableRecord(); - using var sub1 = fixture.Changed.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var changed).Subscribe(); - using var sub2 = fixture.Changing.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var changing).Subscribe(); + using var sub1 = fixture.Changed.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var changed) + .Subscribe(); + using var sub2 = fixture.Changing.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var changing) + .Subscribe(); await AssertCount(0, changing, changed); + var outer = fixture.DelayChangeNotifications(); fixture.Value = 1; - await AssertCount(1, changing, changed); + await AssertCount(0, changing, changed); - using (fixture.DelayChangeNotifications()) - { - fixture.Value = 2; - await AssertCount(1, changing, changed); + var inner = fixture.DelayChangeNotifications(); + fixture.Value = 2; + await AssertCount(0, changing, changed); - fixture.Value = 3; - await AssertCount(1, changing, changed); - } + outer.Dispose(); + await AssertCount(0, changing, changed); // Still delayed by inner - // After disposing, delayed notifications should fire - await AssertCount(2, changing, changed); + inner.Dispose(); + await AssertCount(1, changing, changed); // Now notifications fire } /// - /// Test that PropertyChanging event works correctly. + /// Test that PropertyChanged event works correctly. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task PropertyChangingEventShouldFire() + public async Task PropertyChangedEventShouldFire() { var fixture = new TestMutableRecord(); var fired = false; var propertyName = string.Empty; - fixture.PropertyChanging += (sender, args) => + fixture.PropertyChanged += (sender, args) => { fired = true; propertyName = args.PropertyName ?? string.Empty; @@ -178,17 +144,17 @@ public async Task PropertyChangingEventShouldFire() } /// - /// Test that PropertyChanged event works correctly. + /// Test that PropertyChanging event works correctly. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task PropertyChangedEventShouldFire() + public async Task PropertyChangingEventShouldFire() { var fixture = new TestMutableRecord(); var fired = false; var propertyName = string.Empty; - fixture.PropertyChanged += (sender, args) => + fixture.PropertyChanging += (sender, args) => { fired = true; propertyName = args.PropertyName ?? string.Empty; @@ -204,78 +170,108 @@ public async Task PropertyChangedEventShouldFire() } /// - /// Test that ReactiveRecord should serialize correctly. + /// Test that ReactiveRecord doesn't serialize internal reactive properties. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ReactiveRecordShouldSerializeCorrectly() + public async Task ReactiveRecordShouldNotSerializeInternalReactiveProperties() { var fixture = new TestRecord { Name = "Test", Age = 25 }; var json = JsonSerializer.Serialize(fixture); - await Assert.That(json).Contains("Test"); - await Assert.That(json).Contains("25"); - - var deserialized = JsonSerializer.Deserialize(json); - using (Assert.Multiple()) { - await Assert.That(deserialized).IsNotNull(); - await Assert.That(deserialized!.Name).IsEqualTo("Test"); - await Assert.That(deserialized.Age).IsEqualTo(25); + await Assert.That(json).DoesNotContain("Changing"); + await Assert.That(json).DoesNotContain("Changed"); + await Assert.That(json).DoesNotContain("ThrownExceptions"); } } /// - /// Test that ReactiveRecord doesn't serialize internal reactive properties. + /// Test that ReactiveRecord properties raise changing and changed notifications. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ReactiveRecordShouldNotSerializeInternalReactiveProperties() + public async Task ReactiveRecordShouldRaisePropertyChangeNotifications() + { + var fixture = new TestRecord { Name = "Initial" }; + var changingEvents = new List(); + var changedEvents = new List(); + + using var sub1 = fixture.Changing + .Where(x => x.PropertyName is not null) + .Select(x => x.PropertyName!) + .Subscribe(x => changingEvents.Add(x)); + + using var sub2 = fixture.Changed + .Where(x => x.PropertyName is not null) + .Select(x => x.PropertyName!) + .Subscribe(x => changedEvents.Add(x)); + + var updated = fixture with { Name = "Updated" }; + + // Records create new instances, so we need to subscribe to the new instance + using var sub3 = updated.Changing + .Where(x => x.PropertyName is not null) + .Select(x => x.PropertyName!) + .Subscribe(x => changingEvents.Add(x)); + + using var sub4 = updated.Changed + .Where(x => x.PropertyName is not null) + .Select(x => x.PropertyName!) + .Subscribe(x => changedEvents.Add(x)); + + await Assert.That(updated.Name).IsEqualTo("Updated"); + } + + /// + /// Test that ReactiveRecord should serialize correctly. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ReactiveRecordShouldSerializeCorrectly() { var fixture = new TestRecord { Name = "Test", Age = 25 }; var json = JsonSerializer.Serialize(fixture); + await Assert.That(json).Contains("Test"); + await Assert.That(json).Contains("25"); + + var deserialized = JsonSerializer.Deserialize(json); + using (Assert.Multiple()) { - await Assert.That(json).DoesNotContain("Changing"); - await Assert.That(json).DoesNotContain("Changed"); - await Assert.That(json).DoesNotContain("ThrownExceptions"); + await Assert.That(deserialized).IsNotNull(); + await Assert.That(deserialized!.Name).IsEqualTo("Test"); + await Assert.That(deserialized.Age).IsEqualTo(25); } } /// - /// Test that nested change notifications work correctly. + /// Test that removing PropertyChanged event handler works. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task NestedDelayChangeNotificationsShouldWork() + public async Task RemovingPropertyChangedHandlerShouldWork() { var fixture = new TestMutableRecord(); - using var sub1 = fixture.Changed.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var changed).Subscribe(); - using var sub2 = fixture.Changing.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var changing).Subscribe(); + var callCount = 0; - await AssertCount(0, changing, changed); + void Handler(object? sender, PropertyChangedEventArgs args) => callCount++; - var outer = fixture.DelayChangeNotifications(); + fixture.PropertyChanged += Handler; fixture.Value = 1; - await AssertCount(0, changing, changed); + await Assert.That(callCount).IsEqualTo(1); - var inner = fixture.DelayChangeNotifications(); + fixture.PropertyChanged -= Handler; fixture.Value = 2; - await AssertCount(0, changing, changed); - - outer.Dispose(); - await AssertCount(0, changing, changed); // Still delayed by inner - - inner.Dispose(); - await AssertCount(1, changing, changed); // Now notifications fire + await Assert.That(callCount).IsEqualTo(1); // Should not have incremented } /// - /// Test that removing PropertyChanging event handler works. + /// Test that removing PropertyChanging event handler works. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task RemovingPropertyChangingHandlerShouldWork() { @@ -294,24 +290,33 @@ public async Task RemovingPropertyChangingHandlerShouldWork() } /// - /// Test that removing PropertyChanged event handler works. + /// Test that SuppressChangeNotifications works correctly. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task RemovingPropertyChangedHandlerShouldWork() + public async Task SuppressChangeNotificationsShouldWork() { var fixture = new TestMutableRecord(); - var callCount = 0; - void Handler(object? sender, PropertyChangedEventArgs args) => callCount++; + await Assert.That(fixture.AreChangeNotificationsEnabled()).IsTrue(); - fixture.PropertyChanged += Handler; - fixture.Value = 1; - await Assert.That(callCount).IsEqualTo(1); + using (fixture.SuppressChangeNotifications()) + { + await Assert.That(fixture.AreChangeNotificationsEnabled()).IsFalse(); + } - fixture.PropertyChanged -= Handler; - fixture.Value = 2; - await Assert.That(callCount).IsEqualTo(1); // Should not have incremented + await Assert.That(fixture.AreChangeNotificationsEnabled()).IsTrue(); + } + + /// + /// Test that ThrownExceptions observable is initialized. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ThrownExceptionsObservableShouldBeInitialized() + { + var fixture = new TestRecord(); + await Assert.That(fixture.ThrownExceptions).IsNotNull(); } private static async Task AssertCount(int expected, params ICollection[] collections) @@ -323,7 +328,7 @@ private static async Task AssertCount(int expected, params ICollection[] collect } /// - /// Test record for immutable scenarios. + /// Test record for immutable scenarios. /// private record TestRecord : ReactiveRecord { @@ -333,7 +338,7 @@ private record TestRecord : ReactiveRecord } /// - /// Test record with mutable properties for testing property change notifications. + /// Test record with mutable properties for testing property change notifications. /// private record TestMutableRecord : ReactiveRecord { diff --git a/src/tests/ReactiveUI.Tests/ReactiveProperty/Mocks/ReactivePropertyVM.cs b/src/tests/ReactiveUI.Tests/ReactiveProperties/Mocks/ReactivePropertyVM.cs similarity index 72% rename from src/tests/ReactiveUI.Tests/ReactiveProperty/Mocks/ReactivePropertyVM.cs rename to src/tests/ReactiveUI.Tests/ReactiveProperties/Mocks/ReactivePropertyVM.cs index f85e872d78..b47e3fdde8 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveProperty/Mocks/ReactivePropertyVM.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveProperties/Mocks/ReactivePropertyVM.cs @@ -4,10 +4,9 @@ // See the LICENSE file in the project root for full license information. using System.ComponentModel.DataAnnotations; - using ReactiveUI.Tests.Properties; -namespace ReactiveUI.Tests.ReactiveProperty.Mocks; +namespace ReactiveUI.Tests.ReactiveProperties.Mocks; public class ReactivePropertyVM : ReactiveObject { @@ -19,8 +18,8 @@ public ReactivePropertyVM(IScheduler? scheduler = null) .AddValidation(() => IsRequiredProperty); LengthLessThanFiveProperty = new ReactiveProperty(default, scheduler, false, false) - .AddValidation(() => LengthLessThanFiveProperty) - .AddValidationError(s => string.IsNullOrWhiteSpace(s) ? "required" : null); + .AddValidation(() => LengthLessThanFiveProperty) + .AddValidationError(s => string.IsNullOrWhiteSpace(s) ? "required" : null); TaskValidationTestProperty = new ReactiveProperty(default, scheduler, false, false) .AddValidationError(async s => await Task.FromResult(string.IsNullOrWhiteSpace(s) ? "required" : null)); @@ -28,21 +27,15 @@ public ReactivePropertyVM(IScheduler? scheduler = null) CustomValidationErrorMessageProperty = new ReactiveProperty(default, scheduler, false, false) .AddValidation(() => CustomValidationErrorMessageProperty); - CustomValidationErrorMessageWithDisplayNameProperty = new ReactiveProperty(default, scheduler, false, false) - .AddValidation(() => CustomValidationErrorMessageWithDisplayNameProperty); + CustomValidationErrorMessageWithDisplayNameProperty = + new ReactiveProperty(default, scheduler, false, false) + .AddValidation(() => CustomValidationErrorMessageWithDisplayNameProperty); - CustomValidationErrorMessageWithResourceProperty = new ReactiveProperty(default, scheduler, false, false) - .AddValidation(() => CustomValidationErrorMessageWithResourceProperty); + CustomValidationErrorMessageWithResourceProperty = + new ReactiveProperty(default, scheduler, false, false) + .AddValidation(() => CustomValidationErrorMessageWithResourceProperty); } - [Required(ErrorMessage = "error!")] - public ReactiveProperty IsRequiredProperty { get; } - - [StringLength(5, ErrorMessage = "5over")] - public ReactiveProperty LengthLessThanFiveProperty { get; } - - public ReactiveProperty TaskValidationTestProperty { get; } - [Required(ErrorMessage = "Custom validation error message for {0}")] public ReactiveProperty CustomValidationErrorMessageProperty { get; } @@ -50,7 +43,17 @@ public ReactivePropertyVM(IScheduler? scheduler = null) [Display(Name = "CustomName")] public ReactiveProperty CustomValidationErrorMessageWithDisplayNameProperty { get; } - [Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = nameof(Resources.ValidationErrorMessage))] + [Required( + ErrorMessageResourceType = typeof(Resources), + ErrorMessageResourceName = nameof(Resources.ValidationErrorMessage))] [Display(ResourceType = typeof(Resources), Name = nameof(Resources.ValidationTargetPropertyName))] public ReactiveProperty CustomValidationErrorMessageWithResourceProperty { get; } + + [Required(ErrorMessage = "error!")] + public ReactiveProperty IsRequiredProperty { get; } + + [StringLength(5, ErrorMessage = "5over")] + public ReactiveProperty LengthLessThanFiveProperty { get; } + + public ReactiveProperty TaskValidationTestProperty { get; } } diff --git a/src/tests/ReactiveUI.Tests/ReactiveProperty/Mocks/SubcribeTestViewModel.cs b/src/tests/ReactiveUI.Tests/ReactiveProperties/Mocks/SubcribeTestViewModel.cs similarity index 91% rename from src/tests/ReactiveUI.Tests/ReactiveProperty/Mocks/SubcribeTestViewModel.cs rename to src/tests/ReactiveUI.Tests/ReactiveProperties/Mocks/SubcribeTestViewModel.cs index fc573ba198..d261e838d6 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveProperty/Mocks/SubcribeTestViewModel.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveProperties/Mocks/SubcribeTestViewModel.cs @@ -5,7 +5,7 @@ using System.Diagnostics; -namespace ReactiveUI.Tests.ReactiveProperty.Mocks; +namespace ReactiveUI.Tests.ReactiveProperties.Mocks; public class SubcribeTestViewModel : IDisposable { @@ -14,7 +14,7 @@ public class SubcribeTestViewModel : IDisposable private bool _disposedValue; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The count. public SubcribeTestViewModel(int count) @@ -36,16 +36,16 @@ public SubcribeTestViewModel(int count) public ReactiveProperty Property { get; } = new(1, ImmediateScheduler.Instance, false, false); + public long StartupTime { get; } + public int SubscriberCount { get; } public int SubscriberEvents { get; } - public long StartupTime { get; } - public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + Dispose(true); GC.SuppressFinalize(this); } @@ -70,7 +70,7 @@ private class BasicViewModel(IObservable observable) : IDisposable public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + Dispose(true); GC.SuppressFinalize(this); } diff --git a/src/tests/ReactiveUI.Tests/ReactiveProperty/ReactivePropertyBasicTests.cs b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyBasicTests.cs similarity index 95% rename from src/tests/ReactiveUI.Tests/ReactiveProperty/ReactivePropertyBasicTests.cs rename to src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyBasicTests.cs index bd91bb79b5..fa483db93e 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveProperty/ReactivePropertyBasicTests.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyBasicTests.cs @@ -5,86 +5,65 @@ using System.Collections; -using Microsoft.Reactive.Testing; - -namespace ReactiveUI.Tests.ReactiveProperty; +namespace ReactiveUI.Tests.ReactiveProperties; /// -/// Basic tests for ReactiveProperty covering core functionality. +/// Basic tests for ReactiveProperty covering core functionality. /// public class ReactivePropertyBasicTests { [Test] - public async Task DefaultConstructorCreatesPropertyWithNullValue() + public async Task AddValidationErrorIgnoreInitialError() { - using var rp = ReactiveProperty.Create(); - await Assert.That(rp.Value).IsNull(); - } + using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); + rp.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null, true); - [Test] - public async Task ConstructorWithInitialValueSetsValue() - { - using var rp = ReactiveProperty.Create(42); - await Assert.That(rp.Value).IsEqualTo(42); - } + await Assert.That(rp.HasErrors).IsFalse(); // Initial error ignored - [Test] - public async Task ValuePropertyGetterReturnsCurrentValue() - { - using var rp = ReactiveProperty.Create("test"); - await Assert.That(rp.Value).IsEqualTo("test"); + rp.Value = string.Empty; + await Assert.That(rp.HasErrors).IsTrue(); // Subsequent errors detected } [Test] - public async Task ValuePropertySetterUpdatesValue() + public async Task AddValidationErrorWithEnumerableFunction() { - using var rp = ReactiveProperty.Create(); - rp.Value = "new value"; - await Assert.That(rp.Value).IsEqualTo("new value"); - } + using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); + rp.AddValidationError(x => string.IsNullOrEmpty(x) ? new[] { "Required" } : null); - [Test] - public async Task SubscribeReceivesCurrentValue() - { - using var rp = ReactiveProperty.Create(42, ImmediateScheduler.Instance, false, false); - var received = 0; - rp.Subscribe(x => received = x); + await Assert.That(rp.HasErrors).IsTrue(); - await Assert.That(received).IsEqualTo(42); + rp.Value = "test"; + await Assert.That(rp.HasErrors).IsFalse(); } [Test] - public async Task SubscribeReceivesValueChanges() + public async Task AddValidationErrorWithObservableFunction() { - using var rp = ReactiveProperty.Create(0, ImmediateScheduler.Instance, false, false); - var values = new List(); - rp.Subscribe(x => values.Add(x)); + using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); + rp.AddValidationError(xs => xs.Select(x => string.IsNullOrEmpty(x) ? "Required" : null)); - rp.Value = 1; - rp.Value = 2; + await Assert.That(rp.HasErrors).IsTrue(); - await Assert.That(values).Contains(0); - await Assert.That(values).Contains(1); - await Assert.That(values).Contains(2); + rp.Value = "test"; + await Assert.That(rp.HasErrors).IsFalse(); } [Test] - public async Task SkipCurrentValueOnSubscribe() + public async Task AddValidationErrorWithSyncFunction() { - using var rp = ReactiveProperty.Create(42, ImmediateScheduler.Instance, skipCurrentValueOnSubscribe: true, allowDuplicateValues: false); - var values = new List(); - rp.Subscribe(x => values.Add(x)); + using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); + rp.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null); - await Assert.That(values).IsEmpty(); // Should not receive initial value + await Assert.That(rp.HasErrors).IsTrue(); - rp.Value = 100; - await Assert.That(values).Contains(100); + rp.Value = "test"; + await Assert.That(rp.HasErrors).IsFalse(); } [Test] public async Task AllowDuplicateValuesSendsMultipleIdenticalValues() { - using var rp = ReactiveProperty.Create(0, ImmediateScheduler.Instance, skipCurrentValueOnSubscribe: false, allowDuplicateValues: true); + using var rp = ReactiveProperty.Create(0, ImmediateScheduler.Instance, false, true); var values = new List(); rp.Subscribe(x => values.Add(x)); @@ -95,30 +74,25 @@ public async Task AllowDuplicateValuesSendsMultipleIdenticalValues() } [Test] - public async Task DistinctUntilChangedDoesNotSendDuplicates() + public async Task CheckValidationInvokesValidation() { - using var rp = ReactiveProperty.Create(0, ImmediateScheduler.Instance, skipCurrentValueOnSubscribe: false, allowDuplicateValues: false); - var values = new List(); - rp.Subscribe(x => values.Add(x)); - - var initialCount = values.Count; - - rp.Value = 0; // Same value, should not trigger - await Assert.That(values.Count).IsEqualTo(initialCount); + using var rp = ReactiveProperty.Create(0); + rp.CheckValidation(); // Should not throw + await Assert.That(rp.Value).IsEqualTo(0); } [Test] - public async Task RefreshSendsCurrentValueEvenIfUnchanged() + public async Task ConstructorWithInitialValueSetsValue() { - using var rp = ReactiveProperty.Create(42, ImmediateScheduler.Instance, false, false); - var values = new List(); - rp.Subscribe(x => values.Add(x)); - - var countBefore = values.Count; + using var rp = ReactiveProperty.Create(42); + await Assert.That(rp.Value).IsEqualTo(42); + } - rp.Refresh(); - await Assert.That(values.Count).IsGreaterThan(countBefore); - await Assert.That(values.Last()).IsEqualTo(42); + [Test] + public async Task DefaultConstructorCreatesPropertyWithNullValue() + { + using var rp = ReactiveProperty.Create(); + await Assert.That(rp.Value).IsNull(); } [Test] @@ -132,33 +106,36 @@ public async Task DisposeSetsIsDisposed() } [Test] - public async Task SubscribeAfterDisposeCompletesImmediately() + public async Task DistinctUntilChangedDoesNotSendDuplicates() { - var rp = ReactiveProperty.Create(42); - rp.Dispose(); + using var rp = ReactiveProperty.Create(0, ImmediateScheduler.Instance, false, false); + var values = new List(); + rp.Subscribe(x => values.Add(x)); - var completed = false; - rp.Subscribe( - onNext: _ => { }, - onCompleted: () => completed = true); + var initialCount = values.Count; - await Assert.That(completed).IsTrue(); + rp.Value = 0; // Same value, should not trigger + await Assert.That(values.Count).IsEqualTo(initialCount); } [Test] - public async Task SubscribeWithNullObserverReturnsEmptyDisposable() + public async Task ErrorsChangedEventFires() { - using var rp = ReactiveProperty.Create(); - var disposable = rp.Subscribe(null!); + using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); + var fired = false; - await Assert.That(disposable).IsNotNull(); + rp.ErrorsChanged += (_, _) => fired = true; + rp.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null); + + await Assert.That(fired).IsTrue(); } [Test] - public async Task HasErrorsInitiallyFalse() + public async Task GetErrorsINotifyDataErrorInfoReturnsEmptyWhenNoErrors() { using var rp = ReactiveProperty.Create(); - await Assert.That(rp.HasErrors).IsFalse(); + var errors = ((INotifyDataErrorInfo)rp).GetErrors("Value"); + await Assert.That(errors).IsNotNull(); } [Test] @@ -170,67 +147,46 @@ public async Task GetErrorsReturnsNullWhenNoErrors() } [Test] - public async Task GetErrorsINotifyDataErrorInfoReturnsEmptyWhenNoErrors() + public async Task HasErrorsInitiallyFalse() { using var rp = ReactiveProperty.Create(); - var errors = ((INotifyDataErrorInfo)rp).GetErrors("Value"); - await Assert.That(errors).IsNotNull(); + await Assert.That(rp.HasErrors).IsFalse(); } [Test] - public async Task CheckValidationInvokesValidation() + public async Task MultipleSubscribersReceiveUpdates() { - using var rp = ReactiveProperty.Create(0); - rp.CheckValidation(); // Should not throw - await Assert.That(rp.Value).IsEqualTo(0); - } + using var rp = ReactiveProperty.Create(0, ImmediateScheduler.Instance, false, false); + var values1 = new List(); + var values2 = new List(); - [Test] - public async Task AddValidationErrorWithSyncFunction() - { - using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); - rp.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null); + rp.Subscribe(x => values1.Add(x)); - await Assert.That(rp.HasErrors).IsTrue(); + rp.Value = 1; - rp.Value = "test"; - await Assert.That(rp.HasErrors).IsFalse(); - } + rp.Subscribe(x => values2.Add(x)); - [Test] - public async Task AddValidationErrorWithObservableFunction() - { - using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); - rp.AddValidationError(xs => xs.Select(x => string.IsNullOrEmpty(x) ? "Required" : null)); + rp.Value = 2; - await Assert.That(rp.HasErrors).IsTrue(); + await Assert.That(values1).Contains(0); + await Assert.That(values1).Contains(1); + await Assert.That(values1).Contains(2); - rp.Value = "test"; - await Assert.That(rp.HasErrors).IsFalse(); + await Assert.That(values2).Contains(1); // Gets current value on subscribe + await Assert.That(values2).Contains(2); } [Test] - public async Task AddValidationErrorWithEnumerableFunction() + public async Task MultipleValidationErrorsAreCombined() { using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); - rp.AddValidationError(x => string.IsNullOrEmpty(x) ? new[] { "Required" } : null); + rp.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null!) + .AddValidationError(x => x?.Length < 3 ? "Too short" : null); await Assert.That(rp.HasErrors).IsTrue(); - rp.Value = "test"; - await Assert.That(rp.HasErrors).IsFalse(); - } - - [Test] - public async Task AddValidationErrorIgnoreInitialError() - { - using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); - rp.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null, ignoreInitialError: true); - - await Assert.That(rp.HasErrors).IsFalse(); // Initial error ignored - - rp.Value = string.Empty; - await Assert.That(rp.HasErrors).IsTrue(); // Subsequent errors detected + var errors = rp.GetErrors("Value"); + await Assert.That(errors).IsNotNull(); } [Test] @@ -261,28 +217,69 @@ public async Task ObserveHasErrorsEmitsErrorState() } [Test] - public async Task ErrorsChangedEventFires() + public async Task PropertyChangedEventFires() { - using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); + using var rp = ReactiveProperty.Create(0); var fired = false; + var propertyName = string.Empty; - rp.ErrorsChanged += (_, _) => fired = true; - rp.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null); + ((INotifyPropertyChanged)rp).PropertyChanged += (_, args) => + { + fired = true; + propertyName = args.PropertyName ?? string.Empty; + }; - await Assert.That(fired).IsTrue(); + rp.Value = 42; + + using (Assert.Multiple()) + { + await Assert.That(fired).IsTrue(); + await Assert.That(propertyName).IsEqualTo(nameof(ReactiveProperty.Value)); + } } [Test] - public async Task MultipleValidationErrorsAreCombined() + public async Task RefreshSendsCurrentValueEvenIfUnchanged() { - using var rp = ReactiveProperty.Create(null, ImmediateScheduler.Instance, false, false); - rp.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null!) - .AddValidationError(x => x?.Length < 3 ? "Too short" : null); + using var rp = ReactiveProperty.Create(42, ImmediateScheduler.Instance, false, false); + var values = new List(); + rp.Subscribe(x => values.Add(x)); - await Assert.That(rp.HasErrors).IsTrue(); + var countBefore = values.Count; - var errors = rp.GetErrors("Value"); - await Assert.That(errors).IsNotNull(); + rp.Refresh(); + await Assert.That(values.Count).IsGreaterThan(countBefore); + await Assert.That(values.Last()).IsEqualTo(42); + } + + [Test] + + [TestExecutor] + public async Task SchedulerIsUsedForNotifications() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + using var rp = ReactiveProperty.Create(0, scheduler, false, false); + var values = new List(); + rp.Subscribe(x => values.Add(x)); + + // Value should not be received until scheduler advances + await Assert.That(values).IsEmpty(); + + scheduler.Start(); + await Assert.That(values).Contains(0); + } + + [Test] + public async Task SkipCurrentValueOnSubscribe() + { + using var rp = ReactiveProperty.Create(42, ImmediateScheduler.Instance, true, false); + var values = new List(); + rp.Subscribe(x => values.Add(x)); + + await Assert.That(values).IsEmpty(); // Should not receive initial value + + rp.Value = 100; + await Assert.That(values).Contains(100); } [Test] @@ -302,62 +299,65 @@ public async Task StaticCreateMethodsWork() } [Test] - public async Task SchedulerIsUsedForNotifications() + public async Task SubscribeAfterDisposeCompletesImmediately() { - var scheduler = new TestScheduler(); - using var rp = ReactiveProperty.Create(0, scheduler, false, false); - var values = new List(); - rp.Subscribe(x => values.Add(x)); + var rp = ReactiveProperty.Create(42); + rp.Dispose(); - // Value should not be received until scheduler advances - await Assert.That(values).IsEmpty(); + var completed = false; + rp.Subscribe( + _ => { }, + () => completed = true); - scheduler.Start(); - await Assert.That(values).Contains(0); + await Assert.That(completed).IsTrue(); } [Test] - public async Task PropertyChangedEventFires() + public async Task SubscribeReceivesCurrentValue() { - using var rp = ReactiveProperty.Create(0); - var fired = false; - var propertyName = string.Empty; - - ((INotifyPropertyChanged)rp).PropertyChanged += (_, args) => - { - fired = true; - propertyName = args.PropertyName ?? string.Empty; - }; - - rp.Value = 42; + using var rp = ReactiveProperty.Create(42, ImmediateScheduler.Instance, false, false); + var received = 0; + rp.Subscribe(x => received = x); - using (Assert.Multiple()) - { - await Assert.That(fired).IsTrue(); - await Assert.That(propertyName).IsEqualTo(nameof(ReactiveProperty.Value)); - } + await Assert.That(received).IsEqualTo(42); } [Test] - public async Task MultipleSubscribersReceiveUpdates() + public async Task SubscribeReceivesValueChanges() { using var rp = ReactiveProperty.Create(0, ImmediateScheduler.Instance, false, false); - var values1 = new List(); - var values2 = new List(); - - rp.Subscribe(x => values1.Add(x)); + var values = new List(); + rp.Subscribe(x => values.Add(x)); rp.Value = 1; + rp.Value = 2; - rp.Subscribe(x => values2.Add(x)); + await Assert.That(values).Contains(0); + await Assert.That(values).Contains(1); + await Assert.That(values).Contains(2); + } - rp.Value = 2; + [Test] + public async Task SubscribeWithNullObserverReturnsEmptyDisposable() + { + using var rp = ReactiveProperty.Create(); + var disposable = rp.Subscribe(null!); - await Assert.That(values1).Contains(0); - await Assert.That(values1).Contains(1); - await Assert.That(values1).Contains(2); + await Assert.That(disposable).IsNotNull(); + } - await Assert.That(values2).Contains(1); // Gets current value on subscribe - await Assert.That(values2).Contains(2); + [Test] + public async Task ValuePropertyGetterReturnsCurrentValue() + { + using var rp = ReactiveProperty.Create("test"); + await Assert.That(rp.Value).IsEqualTo("test"); + } + + [Test] + public async Task ValuePropertySetterUpdatesValue() + { + using var rp = ReactiveProperty.Create(); + rp.Value = "new value"; + await Assert.That(rp.Value).IsEqualTo("new value"); } } diff --git a/src/tests/ReactiveUI.Tests/ReactiveProperty/ReactivePropertyTest.cs b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyTest.cs similarity index 94% rename from src/tests/ReactiveUI.Tests/ReactiveProperty/ReactivePropertyTest.cs rename to src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyTest.cs index 27de4160ae..1b64720e57 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveProperty/ReactivePropertyTest.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyTest.cs @@ -4,14 +4,27 @@ // See the LICENSE file in the project root for full license information. using System.Collections; -using Microsoft.Reactive.Testing; -using ReactiveUI.Testing; -using ReactiveUI.Tests.ReactiveProperty.Mocks; +using ReactiveUI.Tests.ReactiveProperties.Mocks; -namespace ReactiveUI.Tests.ReactiveProperty; +namespace ReactiveUI.Tests.ReactiveProperties; -public class ReactivePropertyTest : ReactiveTest +public class ReactivePropertyTest { + [Test] + public async Task CheckValidation() + { + var minValue = 0; + using var rp = new ReactiveProperty(0, ImmediateScheduler.Instance, false, false) + .AddValidationError(x => x < minValue ? "Error" : null); + await Assert.That(rp.GetErrors("Value") == null).IsTrue(); + + minValue = 1; + await Assert.That(rp.GetErrors("Value") == null).IsTrue(); + + rp.CheckValidation(); + await Assert.That(rp.GetErrors("Value")?.OfType()).IsEquivalentTo(["Error"]); + } + [Test] public async Task DefaultValueIsRaisedOnSubscribe() { @@ -20,6 +33,43 @@ public async Task DefaultValueIsRaisedOnSubscribe() rp.Subscribe(async x => await Assert.That(x).IsNull()); } + [Test] + public async Task ErrorsChanged_EventIsRaised() + { + using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) + .AddValidationError(x => string.IsNullOrEmpty(x) ? "error" : null); + + DataErrorsChangedEventArgs? eventArgs = null; + rp.ErrorsChanged += (sender, e) => eventArgs = e; + + rp.Value = "valid"; + await Task.Delay(10); + + await Assert.That(eventArgs).IsNotNull(); + } + + [Test] + public async Task IgnoreInitErrorAndUpdateValue() + { + using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) + .AddValidationError(x => string.IsNullOrEmpty(x) ? "error" : null, true); + + await Assert.That(rp.HasErrors).IsFalse(); + rp.Value = string.Empty; + await Assert.That(rp.HasErrors).IsTrue(); + } + + [Test] + public async Task IgnoreInitialErrorAndCheckValidation() + { + using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) + .AddValidationError(x => string.IsNullOrEmpty(x) ? "error" : null, true); + + await Assert.That(rp.HasErrors).IsFalse(); + rp.CheckValidation(); + await Assert.That(rp.HasErrors).IsTrue(); + } + [Test] public async Task InitialValue() { @@ -40,6 +90,129 @@ public async Task InitialValueSkipCurrent() await Assert.That(rp.Value).IsEqualTo("ReactiveUI 2"); } + [Test] + public async Task MultipleSubscribersGetCurrentValue() + { + using var rp = new ReactiveProperty(0, ImmediateScheduler.Instance, false, false); + var collector1 = new List(); + var collector2 = new List(); + var obs = rp; + obs.Subscribe(x => collector1.Add(x)); + + await Assert.That(rp.Value).IsEqualTo(0); + await Assert.That(collector1).IsEquivalentTo([0]); + + rp.Value = 1; + await Assert.That(rp.Value).IsEqualTo(1); + await Assert.That(collector1).IsEquivalentTo([0, 1]); + + rp.Value = 2; + await Assert.That(rp.Value).IsEqualTo(2); + await Assert.That(collector1).IsEquivalentTo([0, 1, 2]); + + // second subscriber + obs.Subscribe(x => collector2.Add(x)); + await Assert.That(rp.Value).IsEqualTo(2); + await Assert.That(collector2).IsEquivalentTo([2]); + + rp.Value = 3; + await Assert.That(rp.Value).IsEqualTo(3); + await Assert.That(collector1).IsEquivalentTo([0, 1, 2, 3]); + await Assert.That(collector2).IsEquivalentTo([2, 3]); + } + + [Test] + public async Task ObserveErrors() + { + using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) + .AddValidationError(x => x == null ? "Error" : null); + + var results = new List(); + rp.ObserveErrorChanged.Subscribe(results.Add); + rp.Value = "OK"; + + await Assert.That(results.Count).IsEqualTo(2); + await Assert.That(results[0]?.OfType()).IsEquivalentTo(["Error"]); + await Assert.That(results[1] == null).IsTrue(); + } + + [Test] + public async Task ObserveHasError() + { + using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) + .AddValidationError(x => x == null ? "Error" : null); + + var results = new List(); + rp.ObserveHasErrors.Subscribe(x => results.Add(x)); + rp.Value = "OK"; + + await Assert.That(results.Count).IsEqualTo(2); + await Assert.That(results[0]).IsTrue(); + await Assert.That(results[1]).IsFalse(); + } + + [Test] + public async Task ObserveValidationErrors_HandlesMultipleErrors() + { + var target = new ReactivePropertyVM(); + var errors = new List(); + target.LengthLessThanFiveProperty + .ObserveValidationErrors() + .Subscribe(x => errors.Add(x)); + + await Assert.That(errors).Count().IsEqualTo(1); + await Assert.That(errors[0]).IsEqualTo("required"); + + target.LengthLessThanFiveProperty.Value = "ok"; + await Assert.That(errors.Last()).IsNull(); + + target.LengthLessThanFiveProperty.Value = "toolong"; + await Assert.That(errors.Last()).IsEqualTo("5over"); + } + + [Test] + public async Task ObserveValidationErrors_ReturnsErrorMessages() + { + var target = new ReactivePropertyVM(); + var errors = new List(); + target.IsRequiredProperty + .ObserveValidationErrors() + .Subscribe(x => errors.Add(x)); + + await Assert.That(errors).Count().IsEqualTo(1); + await Assert.That(errors[0]).IsEqualTo("error!"); + + target.IsRequiredProperty.Value = "valid"; + await Assert.That(errors).Count().IsEqualTo(2); + await Assert.That(errors[1]).IsNull(); + + target.IsRequiredProperty.Value = null; + await Assert.That(errors).Count().IsEqualTo(3); + await Assert.That(errors[2]).IsEqualTo("error!"); + } + + [Test] + public async Task ObserveValidationErrors_ThrowsOnNull() + { + ReactiveProperty? nullProperty = null; + await Assert.That(() => nullProperty!.ObserveValidationErrors()) + .Throws(); + } + + [Test] + public async Task Refresh() + { + using var rp = new ReactiveProperty(0, ImmediateScheduler.Instance, false, false); + var collector = new List(); + rp.Subscribe(x => collector.Add(x)); + + await Assert.That(collector).IsEquivalentTo([0]); + + // refresh should always produce a value even if it is the same and duplicates are not allowed + rp.Refresh(); + await Assert.That(collector).IsEquivalentTo([0, 0]); + } + [Test] public async Task SetValueRaisesEvents() { @@ -51,30 +224,53 @@ public async Task SetValueRaisesEvents() } [Test] - public async Task ValidationLengthIsCorrectlyHandled() + public async Task Subscribe_WithNullObserver_ReturnsEmptyDisposable() { - var target = new ReactivePropertyVM(); - IEnumerable? error = null; - target.LengthLessThanFiveProperty - .ObserveErrorChanged - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => error = x); + using var rp = new ReactiveProperty("test"); + var result = rp.Subscribe(null!); - await Assert.That(target.LengthLessThanFiveProperty.HasErrors).IsTrue(); - await Assert.That(error!.OfType().First()).IsEqualTo("required"); + await Assert.That(result).IsNotNull(); + } - target.LengthLessThanFiveProperty.Value = "a"; - await Assert.That(target.LengthLessThanFiveProperty.HasErrors).IsFalse(); - await Assert.That(error == null).IsTrue(); + [Test] + public async Task TestMultipleSubstribers() + { + using var vm = new SubcribeTestViewModel(1000); + await Assert.That(vm.SubscriberCount).IsEqualTo(1000); + await Assert.That(vm.StartupTime).IsLessThan(2000); + await Assert.That(vm.SubscriberEvents).IsEqualTo(1000); + } - target.LengthLessThanFiveProperty.Value = "aaaaaa"; - await Assert.That(target.LengthLessThanFiveProperty.HasErrors).IsTrue(); - await Assert.That(error).IsNotNull(); - await Assert.That(error!.OfType().First()).IsEqualTo("5over"); + [Test] + public async Task ValidationErrorChangedTest() + { + var errors = new List(); + using var rprop = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) + .AddValidationError(x => string.IsNullOrWhiteSpace(x) ? "error" : null); - target.LengthLessThanFiveProperty.Value = null; - await Assert.That(target.LengthLessThanFiveProperty.HasErrors).IsTrue(); - await Assert.That(error!.OfType().First()).IsEqualTo("required"); + // old version behavior + rprop.ObserveErrorChanged.Skip(1).Subscribe(errors.Add); + + await Assert.That(errors.Count).IsEqualTo(0); + + rprop.Value = "OK"; + await Assert.That(errors.Count).IsEqualTo(1); + await Assert.That(errors.Last() == null).IsTrue(); + + rprop.Value = null; + await Assert.That(errors.Count).IsEqualTo(2); + await Assert.That(errors.Last()?.OfType()).IsEquivalentTo(["error"]); + } + + [Test] + public async Task ValidationIgnoreInitialErrorAndRefresh() + { + using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) + .AddValidationError(x => string.IsNullOrEmpty(x) ? "error" : null, true); + + await Assert.That(rp.HasErrors).IsFalse(); + rp.Refresh(); + await Assert.That(rp.HasErrors).IsTrue(); } [Test] @@ -101,6 +297,33 @@ public async Task ValidationIsRequiredIsCorrectlyHandled() await Assert.That(target.IsRequiredProperty.HasErrors).IsTrue(); } + [Test] + public async Task ValidationLengthIsCorrectlyHandled() + { + var target = new ReactivePropertyVM(); + IEnumerable? error = null; + target.LengthLessThanFiveProperty + .ObserveErrorChanged + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => error = x); + + await Assert.That(target.LengthLessThanFiveProperty.HasErrors).IsTrue(); + await Assert.That(error!.OfType().First()).IsEqualTo("required"); + + target.LengthLessThanFiveProperty.Value = "a"; + await Assert.That(target.LengthLessThanFiveProperty.HasErrors).IsFalse(); + await Assert.That(error == null).IsTrue(); + + target.LengthLessThanFiveProperty.Value = "aaaaaa"; + await Assert.That(target.LengthLessThanFiveProperty.HasErrors).IsTrue(); + await Assert.That(error).IsNotNull(); + await Assert.That(error!.OfType().First()).IsEqualTo("5over"); + + target.LengthLessThanFiveProperty.Value = null; + await Assert.That(target.LengthLessThanFiveProperty.HasErrors).IsTrue(); + await Assert.That(error!.OfType().First()).IsEqualTo("required"); + } + [Test] public async Task ValidationTaskTest() { @@ -123,45 +346,25 @@ public async Task ValidationTaskTest() } [Test] - public async Task ValidationWithCustomErrorMessage() + public async Task ValidationWithAsyncFailedCase() { - var target = new ReactivePropertyVM(); - target.CustomValidationErrorMessageProperty.Value = string.Empty; - var errorMessage = target? - .CustomValidationErrorMessageProperty? - .GetErrors(nameof(ReactivePropertyVM.CustomValidationErrorMessageProperty))! - .Cast() - .First(); - - await Assert.That(errorMessage).IsEqualTo("Custom validation error message for CustomValidationErrorMessageProperty"); - } + var errorMessage = "error occured!!"; + using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) + .AddValidationError(x => string.IsNullOrEmpty(x) ? null : errorMessage); - [Test] - public async Task ValidationWithCustomErrorMessageWithDisplayName() - { - var target = new ReactivePropertyVM(); - target.CustomValidationErrorMessageWithDisplayNameProperty.Value = string.Empty; - var errorMessage = target - .CustomValidationErrorMessageWithDisplayNameProperty? - .GetErrors(nameof(ReactivePropertyVM.CustomValidationErrorMessageWithDisplayNameProperty))! - .Cast() - .First(); + IEnumerable? error = null; + rp.ObserveErrorChanged + .Subscribe(x => error = x); - await Assert.That(errorMessage).IsEqualTo("Custom validation error message for CustomName"); - } + await Assert.That(rp.HasErrors).IsFalse(); + await Assert.That(error == null).IsTrue(); - [Test] - public async Task ValidationWithCustomErrorMessageWithResource() - { - var target = new ReactivePropertyVM(); - target.CustomValidationErrorMessageWithResourceProperty.Value = string.Empty; - var errorMessage = target - .CustomValidationErrorMessageWithResourceProperty? - .GetErrors(nameof(ReactivePropertyVM.CustomValidationErrorMessageWithResourceProperty))! - .Cast() - .First(); + rp.Value = "dummy"; //--- push value to trigger validation error - await Assert.That(errorMessage).IsEqualTo("Oops!? FromResource is required."); + await Assert.That(rp.HasErrors).IsTrue(); + await Assert.That(error).IsNotNull(); + await Assert.That(error!.OfType()).IsEquivalentTo([errorMessage]); + await Assert.That(rp.GetErrors("Value")!.OfType()).IsEquivalentTo([errorMessage]); } [Test] @@ -186,162 +389,86 @@ public async Task ValidationWithAsyncSuccessCase() } [Test] - public async Task ValidationWithAsyncFailedCase() - { - var errorMessage = "error occured!!"; - using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) - .AddValidationError(x => string.IsNullOrEmpty(x) ? null : errorMessage); - - IEnumerable? error = null; - rp.ObserveErrorChanged - .Subscribe(x => error = x); - - await Assert.That(rp.HasErrors).IsFalse(); - await Assert.That(error == null).IsTrue(); - - rp.Value = "dummy"; //--- push value to trigger validation error - - await Assert.That(rp.HasErrors).IsTrue(); - await Assert.That(error).IsNotNull(); - await Assert.That(error!.OfType()).IsEquivalentTo([errorMessage]); - await Assert.That(rp.GetErrors("Value")!.OfType()).IsEquivalentTo([errorMessage]); - } - [Test] + [TestExecutor] public async Task ValidationWithAsyncThrottleTest() { - var scheduler = new TestScheduler(); + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); using var rp = new ReactiveProperty(default, scheduler, false, false) - .AddValidationError(xs => xs - .Throttle(TimeSpan.FromSeconds(1), scheduler) - .Select(x => string.IsNullOrEmpty(x) ? "required" : null)); + .AddValidationError(xs => xs + .Throttle(TimeSpan.FromSeconds(1), scheduler) + .Select(x => string.IsNullOrEmpty(x) ? "required" : null)); IEnumerable? error = null; rp.ObserveErrorChanged.Subscribe(x => error = x); - scheduler.AdvanceTo(TimeSpan.FromMilliseconds(0).Ticks); + scheduler.AdvanceTo(DateTimeOffset.MinValue.Add(TimeSpan.FromMilliseconds(0))); rp.Value = string.Empty; await Assert.That(rp.HasErrors).IsFalse(); await Assert.That(error == null).IsTrue(); - scheduler.AdvanceTo(TimeSpan.FromMilliseconds(300).Ticks); + scheduler.AdvanceTo(DateTimeOffset.MinValue.Add(TimeSpan.FromMilliseconds(300))); rp.Value = "a"; await Assert.That(rp.HasErrors).IsFalse(); await Assert.That(error == null).IsTrue(); - scheduler.AdvanceTo(TimeSpan.FromMilliseconds(700).Ticks); + scheduler.AdvanceTo(DateTimeOffset.MinValue.Add(TimeSpan.FromMilliseconds(700))); rp.Value = "b"; await Assert.That(rp.HasErrors).IsFalse(); await Assert.That(error == null).IsTrue(); - scheduler.AdvanceTo(TimeSpan.FromMilliseconds(1100).Ticks); + scheduler.AdvanceTo(DateTimeOffset.MinValue.Add(TimeSpan.FromMilliseconds(1100))); rp.Value = string.Empty; await Assert.That(rp.HasErrors).IsFalse(); await Assert.That(error == null).IsTrue(); - scheduler.AdvanceTo(TimeSpan.FromMilliseconds(2500).Ticks); + scheduler.AdvanceTo(DateTimeOffset.MinValue.Add(TimeSpan.FromMilliseconds(2500))); await Assert.That(rp.HasErrors).IsTrue(); await Assert.That(error).IsNotNull(); await Assert.That(error!.OfType()).IsEquivalentTo(["required"]); } [Test] - public async Task ValidationErrorChangedTest() - { - var errors = new List(); - using var rprop = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) - .AddValidationError(x => string.IsNullOrWhiteSpace(x) ? "error" : null); - - // old version behavior - rprop.ObserveErrorChanged.Skip(1).Subscribe(errors.Add); - - await Assert.That(errors.Count).IsEqualTo(0); - - rprop.Value = "OK"; - await Assert.That(errors.Count).IsEqualTo(1); - await Assert.That(errors.Last() == null).IsTrue(); - - rprop.Value = null; - await Assert.That(errors.Count).IsEqualTo(2); - await Assert.That(errors.Last()?.OfType()).IsEquivalentTo(["error"]); - } - - [Test] - public async Task ValidationIgnoreInitialErrorAndRefresh() - { - using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) - .AddValidationError(x => string.IsNullOrEmpty(x) ? "error" : null, true); - - await Assert.That(rp.HasErrors).IsFalse(); - rp.Refresh(); - await Assert.That(rp.HasErrors).IsTrue(); - } - - [Test] - public async Task IgnoreInitialErrorAndCheckValidation() - { - using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) - .AddValidationError(x => string.IsNullOrEmpty(x) ? "error" : null, true); - - await Assert.That(rp.HasErrors).IsFalse(); - rp.CheckValidation(); - await Assert.That(rp.HasErrors).IsTrue(); - } - - [Test] - public async Task IgnoreInitErrorAndUpdateValue() - { - using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) - .AddValidationError(x => string.IsNullOrEmpty(x) ? "error" : null, true); - - await Assert.That(rp.HasErrors).IsFalse(); - rp.Value = string.Empty; - await Assert.That(rp.HasErrors).IsTrue(); - } - - [Test] - public async Task ObserveErrors() + public async Task ValidationWithCustomErrorMessage() { - using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) - .AddValidationError(x => x == null ? "Error" : null); - - var results = new List(); - rp.ObserveErrorChanged.Subscribe(results.Add); - rp.Value = "OK"; + var target = new ReactivePropertyVM(); + target.CustomValidationErrorMessageProperty.Value = string.Empty; + var errorMessage = target? + .CustomValidationErrorMessageProperty? + .GetErrors(nameof(ReactivePropertyVM.CustomValidationErrorMessageProperty))! + .Cast() + .First(); - await Assert.That(results.Count).IsEqualTo(2); - await Assert.That(results[0]?.OfType()).IsEquivalentTo(["Error"]); - await Assert.That(results[1] == null).IsTrue(); + await Assert.That(errorMessage) + .IsEqualTo("Custom validation error message for CustomValidationErrorMessageProperty"); } [Test] - public async Task ObserveHasError() + public async Task ValidationWithCustomErrorMessageWithDisplayName() { - using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) - .AddValidationError(x => x == null ? "Error" : null); - - var results = new List(); - rp.ObserveHasErrors.Subscribe(x => results.Add(x)); - rp.Value = "OK"; + var target = new ReactivePropertyVM(); + target.CustomValidationErrorMessageWithDisplayNameProperty.Value = string.Empty; + var errorMessage = target + .CustomValidationErrorMessageWithDisplayNameProperty? + .GetErrors(nameof(ReactivePropertyVM.CustomValidationErrorMessageWithDisplayNameProperty))! + .Cast() + .First(); - await Assert.That(results.Count).IsEqualTo(2); - await Assert.That(results[0]).IsTrue(); - await Assert.That(results[1]).IsFalse(); + await Assert.That(errorMessage).IsEqualTo("Custom validation error message for CustomName"); } [Test] - public async Task CheckValidation() + public async Task ValidationWithCustomErrorMessageWithResource() { - var minValue = 0; - using var rp = new ReactiveProperty(0, ImmediateScheduler.Instance, false, false) - .AddValidationError(x => x < minValue ? "Error" : null); - await Assert.That(rp.GetErrors("Value") == null).IsTrue(); - - minValue = 1; - await Assert.That(rp.GetErrors("Value") == null).IsTrue(); + var target = new ReactivePropertyVM(); + target.CustomValidationErrorMessageWithResourceProperty.Value = string.Empty; + var errorMessage = target + .CustomValidationErrorMessageWithResourceProperty? + .GetErrors(nameof(ReactivePropertyVM.CustomValidationErrorMessageWithResourceProperty))! + .Cast() + .First(); - rp.CheckValidation(); - await Assert.That(rp.GetErrors("Value")?.OfType()).IsEquivalentTo(["Error"]); + await Assert.That(errorMessage).IsEqualTo("Oops!? FromResource is required."); } [Test] @@ -367,20 +494,6 @@ public async Task ValueUpdatesMultipleTimesWithDifferentValues() await Assert.That(collector).IsEquivalentTo([0, 1, 2, 3]); } - [Test] - public async Task Refresh() - { - using var rp = new ReactiveProperty(0, ImmediateScheduler.Instance, false, false); - var collector = new List(); - rp.Subscribe(x => collector.Add(x)); - - await Assert.That(collector).IsEquivalentTo([0]); - - // refresh should always produce a value even if it is the same and duplicates are not allowed - rp.Refresh(); - await Assert.That(collector).IsEquivalentTo([0, 0]); - } - [Test] public async Task ValueUpdatesMultipleTimesWithSameValues() { @@ -403,116 +516,4 @@ public async Task ValueUpdatesMultipleTimesWithSameValues() await Assert.That(rp.Value).IsEqualTo(0); await Assert.That(collector).IsEquivalentTo([0, 0, 0, 0]); } - - [Test] - public async Task MultipleSubscribersGetCurrentValue() - { - using var rp = new ReactiveProperty(0, ImmediateScheduler.Instance, false, false); - var collector1 = new List(); - var collector2 = new List(); - var obs = rp; - obs.Subscribe(x => collector1.Add(x)); - - await Assert.That(rp.Value).IsEqualTo(0); - await Assert.That(collector1).IsEquivalentTo([0]); - - rp.Value = 1; - await Assert.That(rp.Value).IsEqualTo(1); - await Assert.That(collector1).IsEquivalentTo([0, 1]); - - rp.Value = 2; - await Assert.That(rp.Value).IsEqualTo(2); - await Assert.That(collector1).IsEquivalentTo([0, 1, 2]); - - // second subscriber - obs.Subscribe(x => collector2.Add(x)); - await Assert.That(rp.Value).IsEqualTo(2); - await Assert.That(collector2).IsEquivalentTo([2]); - - rp.Value = 3; - await Assert.That(rp.Value).IsEqualTo(3); - await Assert.That(collector1).IsEquivalentTo([0, 1, 2, 3]); - await Assert.That(collector2).IsEquivalentTo([2, 3]); - } - - [Test] - public async Task TestMultipleSubstribers() - { - using var vm = new SubcribeTestViewModel(1000); - await Assert.That(vm.SubscriberCount).IsEqualTo(1000); - await Assert.That(vm.StartupTime).IsLessThan(2000); - await Assert.That(vm.SubscriberEvents).IsEqualTo(1000); - } - - [Test] - public async Task ObserveValidationErrors_ReturnsErrorMessages() - { - var target = new ReactivePropertyVM(); - var errors = new List(); - target.IsRequiredProperty - .ObserveValidationErrors() - .Subscribe(x => errors.Add(x)); - - await Assert.That(errors).Count().IsEqualTo(1); - await Assert.That(errors[0]).IsEqualTo("error!"); - - target.IsRequiredProperty.Value = "valid"; - await Assert.That(errors).Count().IsEqualTo(2); - await Assert.That(errors[1]).IsNull(); - - target.IsRequiredProperty.Value = null; - await Assert.That(errors).Count().IsEqualTo(3); - await Assert.That(errors[2]).IsEqualTo("error!"); - } - - [Test] - public async Task ObserveValidationErrors_HandlesMultipleErrors() - { - var target = new ReactivePropertyVM(); - var errors = new List(); - target.LengthLessThanFiveProperty - .ObserveValidationErrors() - .Subscribe(x => errors.Add(x)); - - await Assert.That(errors).Count().IsEqualTo(1); - await Assert.That(errors[0]).IsEqualTo("required"); - - target.LengthLessThanFiveProperty.Value = "ok"; - await Assert.That(errors.Last()).IsNull(); - - target.LengthLessThanFiveProperty.Value = "toolong"; - await Assert.That(errors.Last()).IsEqualTo("5over"); - } - - [Test] - public async Task ObserveValidationErrors_ThrowsOnNull() - { - ReactiveProperty? nullProperty = null; - await Assert.That(() => nullProperty!.ObserveValidationErrors()) - .Throws(); - } - - [Test] - public async Task Subscribe_WithNullObserver_ReturnsEmptyDisposable() - { - using var rp = new ReactiveProperty("test"); - var result = rp.Subscribe(null!); - - await Assert.That(result).IsNotNull(); - } - - [Test] - public async Task ErrorsChanged_EventIsRaised() - { - using var rp = new ReactiveProperty(default, ImmediateScheduler.Instance, false, false) - .AddValidationError(x => string.IsNullOrEmpty(x) ? "error" : null); - - DataErrorsChangedEventArgs? eventArgs = null; - rp.ErrorsChanged += (sender, e) => eventArgs = e; - - rp.Value = "valid"; - await Task.Delay(10); - - await Assert.That(eventArgs).IsNotNull(); - } } diff --git a/src/tests/ReactiveUI.Tests/ReactiveProperty/TestEnum.cs b/src/tests/ReactiveUI.Tests/ReactiveProperties/TestEnum.cs similarity index 87% rename from src/tests/ReactiveUI.Tests/ReactiveProperty/TestEnum.cs rename to src/tests/ReactiveUI.Tests/ReactiveProperties/TestEnum.cs index ec47bfaccb..17e152b96d 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveProperty/TestEnum.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveProperties/TestEnum.cs @@ -3,11 +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. -namespace ReactiveUI.Tests.ReactiveProperty; +namespace ReactiveUI.Tests.ReactiveProperties; internal enum TestEnum { None, Enum1, Enum2 -} \ No newline at end of file +} diff --git a/src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj b/src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj index b67ccea673..79ef7964b1 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj +++ b/src/tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj @@ -11,7 +11,8 @@ $(NoWarn);CS1591 - + + @@ -21,6 +22,10 @@ + + + + diff --git a/src/tests/ReactiveUI.NonParallel.Tests/ReflectionTest.cs b/src/tests/ReactiveUI.Tests/ReflectionTest.cs similarity index 67% rename from src/tests/ReactiveUI.NonParallel.Tests/ReflectionTest.cs rename to src/tests/ReactiveUI.Tests/ReflectionTest.cs index 9f5b0bb195..1c041e346d 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/ReflectionTest.cs +++ b/src/tests/ReactiveUI.Tests/ReflectionTest.cs @@ -3,35 +3,36 @@ // 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.Linq.Expressions; using System.Reflection; +using ReactiveUI.Tests.ReactiveObjects.Mocks; +using ReactiveUI.Tests.WhenAny.Mockups; namespace ReactiveUI.Tests; /// -/// Tests for . +/// Tests for . /// [NotInParallel] public class ReflectionTest { /// - /// Tests that ExpressionToPropertyNames converts simple property access. + /// Tests that ExpressionToPropertyNames converts deeply nested property access. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ExpressionToPropertyNames_SimpleProperty_ReturnsPropertyName() + public async Task ExpressionToPropertyNames_DeeplyNestedProperty_ReturnsFullPath() { - Expression> expression = x => x.IsOnlyOneWord; + Expression> expression = x => x.Child!.IsOnlyOneWord; var result = Reflection.ExpressionToPropertyNames(expression.Body); - await Assert.That(result).IsEqualTo("IsOnlyOneWord"); + await Assert.That(result).IsEqualTo("Child.IsOnlyOneWord"); } /// - /// Tests that ExpressionToPropertyNames converts nested property access. + /// Tests that ExpressionToPropertyNames converts nested property access. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ExpressionToPropertyNames_NestedProperty_ReturnsPropertyPath() { @@ -43,51 +44,64 @@ public async Task ExpressionToPropertyNames_NestedProperty_ReturnsPropertyPath() } /// - /// Tests that ExpressionToPropertyNames converts deeply nested property access. + /// Tests that ExpressionToPropertyNames throws for null expression. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ExpressionToPropertyNames_DeeplyNestedProperty_ReturnsFullPath() + public async Task ExpressionToPropertyNames_NullExpression_Throws() => + await Assert.That(() => Reflection.ExpressionToPropertyNames(null)) + .Throws(); + + /// + /// Tests that ExpressionToPropertyNames converts simple property access. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ExpressionToPropertyNames_SimpleProperty_ReturnsPropertyName() { - Expression> expression = x => x.Child!.IsOnlyOneWord; + Expression> expression = x => x.IsOnlyOneWord; var result = Reflection.ExpressionToPropertyNames(expression.Body); - await Assert.That(result).IsEqualTo("Child.IsOnlyOneWord"); + await Assert.That(result).IsEqualTo("IsOnlyOneWord"); } /// - /// Tests that ExpressionToPropertyNames throws for null expression. + /// Tests that GetEventArgsTypeForEvent throws for invalid event. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ExpressionToPropertyNames_NullExpression_Throws() - { - await Assert.That(() => Reflection.ExpressionToPropertyNames(null)) + public async Task GetEventArgsTypeForEvent_InvalidEvent_Throws() => + await Assert.That(() => Reflection.GetEventArgsTypeForEvent(typeof(TestClassWithEvent), "NonExistentEvent")) + .Throws(); + + /// + /// Tests that GetEventArgsTypeForEvent throws for null type. + /// + /// A representing the asynchronous operation. + [Test] + public async Task GetEventArgsTypeForEvent_NullType_Throws() => + await Assert.That(() => Reflection.GetEventArgsTypeForEvent(null!, "TestEvent")) .Throws(); - } /// - /// Tests that GetValueFetcherForProperty returns fetcher for property. + /// Tests that GetEventArgsTypeForEvent returns EventArgs type. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueFetcherForProperty_Property_ReturnsFetcher() + public async Task GetEventArgsTypeForEvent_ValidEvent_ReturnsEventArgsType() { - var fixture = new TestFixture { IsOnlyOneWord = "Test" }; - var propertyInfo = typeof(TestFixture).GetProperty(nameof(TestFixture.IsOnlyOneWord))!; - - var fetcher = Reflection.GetValueFetcherForProperty(propertyInfo); + var result = Reflection.GetEventArgsTypeForEvent( + typeof(TestClassWithEvent), + nameof(TestClassWithEvent.TestEvent)); - await Assert.That(fetcher).IsNotNull(); - var value = fetcher!(fixture, null); - await Assert.That(value).IsEqualTo("Test"); + await Assert.That(result).IsEqualTo(typeof(EventArgs)); } /// - /// Tests that GetValueFetcherForProperty returns fetcher for field. + /// Tests that GetValueFetcherForProperty returns fetcher for field. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task GetValueFetcherForProperty_Field_ReturnsFetcher() { @@ -102,65 +116,61 @@ public async Task GetValueFetcherForProperty_Field_ReturnsFetcher() } /// - /// Tests that GetValueFetcherForProperty throws for null member. + /// Tests that GetValueFetcherForProperty throws for null member. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueFetcherForProperty_NullMember_Throws() - { + public async Task GetValueFetcherForProperty_NullMember_Throws() => await Assert.That(() => Reflection.GetValueFetcherForProperty(null)) .Throws(); - } /// - /// Tests that GetValueFetcherOrThrow returns fetcher for valid property. + /// Tests that GetValueFetcherForProperty returns fetcher for property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueFetcherOrThrow_ValidProperty_ReturnsFetcher() + public async Task GetValueFetcherForProperty_Property_ReturnsFetcher() { var fixture = new TestFixture { IsOnlyOneWord = "Test" }; var propertyInfo = typeof(TestFixture).GetProperty(nameof(TestFixture.IsOnlyOneWord))!; - var fetcher = Reflection.GetValueFetcherOrThrow(propertyInfo); + var fetcher = Reflection.GetValueFetcherForProperty(propertyInfo); await Assert.That(fetcher).IsNotNull(); - var value = fetcher(fixture, null); + var value = fetcher!(fixture, null); await Assert.That(value).IsEqualTo("Test"); } /// - /// Tests that GetValueFetcherOrThrow throws for null member. + /// Tests that GetValueFetcherOrThrow throws for null member. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueFetcherOrThrow_NullMember_Throws() - { + public async Task GetValueFetcherOrThrow_NullMember_Throws() => await Assert.That(() => Reflection.GetValueFetcherOrThrow(null)) .Throws(); - } /// - /// Tests that GetValueSetterForProperty returns setter for property. + /// Tests that GetValueFetcherOrThrow returns fetcher for valid property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueSetterForProperty_Property_ReturnsSetter() + public async Task GetValueFetcherOrThrow_ValidProperty_ReturnsFetcher() { - var fixture = new TestFixture(); + var fixture = new TestFixture { IsOnlyOneWord = "Test" }; var propertyInfo = typeof(TestFixture).GetProperty(nameof(TestFixture.IsOnlyOneWord))!; - var setter = Reflection.GetValueSetterForProperty(propertyInfo); + var fetcher = Reflection.GetValueFetcherOrThrow(propertyInfo); - await Assert.That(setter).IsNotNull(); - setter(fixture, "NewValue", null); - await Assert.That(fixture.IsOnlyOneWord).IsEqualTo("NewValue"); + await Assert.That(fetcher).IsNotNull(); + var value = fetcher(fixture, null); + await Assert.That(value).IsEqualTo("Test"); } /// - /// Tests that GetValueSetterForProperty returns setter for field. + /// Tests that GetValueSetterForProperty returns setter for field. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task GetValueSetterForProperty_Field_ReturnsSetter() { @@ -175,27 +185,25 @@ public async Task GetValueSetterForProperty_Field_ReturnsSetter() } /// - /// Tests that GetValueSetterForProperty throws for null member. + /// Tests that GetValueSetterForProperty throws for null member. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueSetterForProperty_NullMember_Throws() - { + public async Task GetValueSetterForProperty_NullMember_Throws() => await Assert.That(() => Reflection.GetValueSetterForProperty(null)) .Throws(); - } /// - /// Tests that GetValueSetterOrThrow returns setter for valid property. + /// Tests that GetValueSetterForProperty returns setter for property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueSetterOrThrow_ValidProperty_ReturnsSetter() + public async Task GetValueSetterForProperty_Property_ReturnsSetter() { var fixture = new TestFixture(); var propertyInfo = typeof(TestFixture).GetProperty(nameof(TestFixture.IsOnlyOneWord))!; - var setter = Reflection.GetValueSetterOrThrow(propertyInfo); + var setter = Reflection.GetValueSetterForProperty(propertyInfo); await Assert.That(setter).IsNotNull(); setter(fixture, "NewValue", null); @@ -203,372 +211,356 @@ public async Task GetValueSetterOrThrow_ValidProperty_ReturnsSetter() } /// - /// Tests that GetValueSetterOrThrow throws for null member. + /// Tests that GetValueSetterOrThrow throws for null member. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetValueSetterOrThrow_NullMember_Throws() - { + public async Task GetValueSetterOrThrow_NullMember_Throws() => await Assert.That(() => Reflection.GetValueSetterOrThrow(null)) .Throws(); - } - - /// - /// Tests that TryGetValueForPropertyChain gets value from simple property. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryGetValueForPropertyChain_SimpleProperty_ReturnsValue() - { - var fixture = new TestFixture { IsOnlyOneWord = "Test" }; - Expression> expression = x => x.IsOnlyOneWord; - var chain = expression.Body.GetExpressionChain(); - - var success = Reflection.TryGetValueForPropertyChain(out var value, fixture, chain); - - await Assert.That(success).IsTrue(); - await Assert.That(value).IsEqualTo("Test"); - } - - /// - /// Tests that TryGetValueForPropertyChain gets value from nested property. - /// - /// A representing the asynchronous operation. - [Test] - public async Task TryGetValueForPropertyChain_NestedProperty_ReturnsValue() - { - var fixture = new HostTestFixture { Child = new TestFixture { IsOnlyOneWord = "NestedTest" } }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var chain = expression.Body.GetExpressionChain(); - - var success = Reflection.TryGetValueForPropertyChain(out var value, fixture, chain); - - await Assert.That(success).IsTrue(); - await Assert.That(value).IsEqualTo("NestedTest"); - } /// - /// Tests that TryGetValueForPropertyChain returns false when null in chain. + /// Tests that GetValueSetterOrThrow returns setter for valid property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TryGetValueForPropertyChain_NullInChain_ReturnsFalse() + public async Task GetValueSetterOrThrow_ValidProperty_ReturnsSetter() { - var fixture = new HostTestFixture { Child = null }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var chain = expression.Body.GetExpressionChain(); + var fixture = new TestFixture(); + var propertyInfo = typeof(TestFixture).GetProperty(nameof(TestFixture.IsOnlyOneWord))!; - var success = Reflection.TryGetValueForPropertyChain(out var value, fixture, chain); + var setter = Reflection.GetValueSetterOrThrow(propertyInfo); - await Assert.That(success).IsFalse(); - await Assert.That(value).IsNull(); + await Assert.That(setter).IsNotNull(); + setter(fixture, "NewValue", null); + await Assert.That(fixture.IsOnlyOneWord).IsEqualTo("NewValue"); } /// - /// Tests that TryGetAllValuesForPropertyChain gets all values in chain. + /// Tests that IsStatic returns false for instance property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TryGetAllValuesForPropertyChain_ValidChain_ReturnsAllValues() + public async Task IsStatic_InstanceProperty_ReturnsFalse() { - var fixture = new HostTestFixture { Child = new TestFixture { IsOnlyOneWord = "Test" } }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var chain = expression.Body.GetExpressionChain(); + var propertyInfo = + typeof(TestClassWithStaticProperty).GetProperty(nameof(TestClassWithStaticProperty.InstanceProperty))!; - var success = Reflection.TryGetAllValuesForPropertyChain(out var values, fixture, chain); + var result = propertyInfo.IsStatic(); - await Assert.That(success).IsTrue(); - await Assert.That(values).Count().IsEqualTo(2); - await Assert.That(values[0].Sender).IsEqualTo(fixture); - await Assert.That(values[1].Value).IsEqualTo("Test"); + await Assert.That(result).IsFalse(); } /// - /// Tests that TryGetAllValuesForPropertyChain returns false when null in chain. + /// Tests that IsStatic throws for null property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TryGetAllValuesForPropertyChain_NullInChain_ReturnsFalse() + public async Task IsStatic_NullProperty_Throws() { - var fixture = new HostTestFixture { Child = null }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var chain = expression.Body.GetExpressionChain(); - - var success = Reflection.TryGetAllValuesForPropertyChain(out var values, fixture, chain); + PropertyInfo propertyInfo = null!; - await Assert.That(success).IsFalse(); + await Assert.That(() => propertyInfo.IsStatic()) + .Throws(); } /// - /// Tests that TrySetValueToPropertyChain sets value on simple property. + /// Tests that IsStatic returns true for static property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TrySetValueToPropertyChain_SimpleProperty_SetsValue() + public async Task IsStatic_StaticProperty_ReturnsTrue() { - var fixture = new TestFixture(); - Expression> expression = x => x.IsOnlyOneWord; - var chain = expression.Body.GetExpressionChain(); + var propertyInfo = + typeof(TestClassWithStaticProperty).GetProperty(nameof(TestClassWithStaticProperty.StaticProperty))!; - var success = Reflection.TrySetValueToPropertyChain(fixture, chain, "NewValue"); + var result = propertyInfo.IsStatic(); - await Assert.That(success).IsTrue(); - await Assert.That(fixture.IsOnlyOneWord).IsEqualTo("NewValue"); + await Assert.That(result).IsTrue(); } /// - /// Tests that TrySetValueToPropertyChain sets value on nested property. + /// Tests that ReallyFindType caches types. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TrySetValueToPropertyChain_NestedProperty_SetsValue() + public async Task ReallyFindType_CachesTypes() { - var fixture = new HostTestFixture { Child = new TestFixture() }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var chain = expression.Body.GetExpressionChain(); + var typeName = typeof(TestFixture).AssemblyQualifiedName; - var success = Reflection.TrySetValueToPropertyChain(fixture, chain, "NestedValue"); + var result1 = Reflection.ReallyFindType(typeName, false); + var result2 = Reflection.ReallyFindType(typeName, false); - await Assert.That(success).IsTrue(); - await Assert.That(fixture.Child.IsOnlyOneWord).IsEqualTo("NestedValue"); + await Assert.That(result1).IsEqualTo(result2); + await Assert.That(result1).IsEqualTo(typeof(TestFixture)); } /// - /// Tests that TrySetValueToPropertyChain returns false when null in chain. + /// Tests that ReallyFindType returns null for invalid type when not throwing. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TrySetValueToPropertyChain_NullInChain_ReturnsFalse() + public async Task ReallyFindType_InvalidTypeNoThrow_ReturnsNull() { - var fixture = new HostTestFixture { Child = null }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var chain = expression.Body.GetExpressionChain(); - - var success = Reflection.TrySetValueToPropertyChain(fixture, chain, "Value", shouldThrow: false); + var result = Reflection.ReallyFindType("InvalidType.DoesNotExist", false); - await Assert.That(success).IsFalse(); + await Assert.That(result).IsNull(); } /// - /// Tests that TrySetValueToPropertyChain throws when shouldThrow is true and target is null. + /// Tests that ReallyFindType throws for invalid type when throwing. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task TrySetValueToPropertyChain_NullTargetWithThrow_Throws() - { - Expression> expression = x => x.Child!.IsOnlyOneWord; - var chain = expression.Body.GetExpressionChain(); - - await Assert.That(() => Reflection.TrySetValueToPropertyChain(null, chain, "Value", shouldThrow: true)) - .Throws(); - } + public async Task ReallyFindType_InvalidTypeWithThrow_Throws() => + await Assert.That(() => Reflection.ReallyFindType("InvalidType.DoesNotExist", true)) + .Throws(); /// - /// Tests that ReallyFindType finds a valid type. + /// Tests that ReallyFindType finds a valid type. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ReallyFindType_ValidType_ReturnsType() { var typeName = typeof(TestFixture).AssemblyQualifiedName; - var result = Reflection.ReallyFindType(typeName, throwOnFailure: false); + var result = Reflection.ReallyFindType(typeName, false); await Assert.That(result).IsNotNull(); await Assert.That(result).IsEqualTo(typeof(TestFixture)); } /// - /// Tests that ReallyFindType returns null for invalid type when not throwing. + /// Tests that Rewrite simplifies expression. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ReallyFindType_InvalidTypeNoThrow_ReturnsNull() + public async Task Rewrite_Expression_SimplifiesExpression() { - var result = Reflection.ReallyFindType("InvalidType.DoesNotExist", throwOnFailure: false); + Expression> expression = x => x.IsOnlyOneWord; - await Assert.That(result).IsNull(); + var result = Reflection.Rewrite(expression.Body); + + await Assert.That(result).IsNotNull(); } /// - /// Tests that ReallyFindType throws for invalid type when throwing. + /// Tests that ThrowIfMethodsNotOverloaded throws for missing methods. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ReallyFindType_InvalidTypeWithThrow_Throws() + public async Task ThrowIfMethodsNotOverloaded_MissingMethod_Throws() { - await Assert.That(() => Reflection.ReallyFindType("InvalidType.DoesNotExist", throwOnFailure: true)) - .Throws(); + var target = new TestClassWithOverriddenMethods(); + + await Assert.That(() => Reflection.ThrowIfMethodsNotOverloaded("TestCaller", target, "NonExistentMethod")) + .Throws(); } /// - /// Tests that ReallyFindType caches types. + /// Tests that ThrowIfMethodsNotOverloaded passes for overloaded methods. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ReallyFindType_CachesTypes() + public async Task ThrowIfMethodsNotOverloaded_OverloadedMethods_DoesNotThrow() { - var typeName = typeof(TestFixture).AssemblyQualifiedName; + var target = new TestClassWithOverriddenMethods(); - var result1 = Reflection.ReallyFindType(typeName, throwOnFailure: false); - var result2 = Reflection.ReallyFindType(typeName, throwOnFailure: false); + Reflection.ThrowIfMethodsNotOverloaded("TestCaller", target, nameof(TestClassWithOverriddenMethods.TestMethod)); - await Assert.That(result1).IsEqualTo(result2); - await Assert.That(result1).IsEqualTo(typeof(TestFixture)); + // If we got here without throwing, the test passes + await Task.CompletedTask; } /// - /// Tests that GetEventArgsTypeForEvent returns EventArgs type. + /// Tests that TryGetAllValuesForPropertyChain returns false when null in chain. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetEventArgsTypeForEvent_ValidEvent_ReturnsEventArgsType() + public async Task TryGetAllValuesForPropertyChain_NullInChain_ReturnsFalse() { - var result = Reflection.GetEventArgsTypeForEvent(typeof(TestClassWithEvent), nameof(TestClassWithEvent.TestEvent)); + var fixture = new HostTestFixture { Child = null }; + Expression> expression = x => x.Child!.IsOnlyOneWord; + var chain = expression.Body.GetExpressionChain(); - await Assert.That(result).IsEqualTo(typeof(EventArgs)); + var success = Reflection.TryGetAllValuesForPropertyChain(out var values, fixture, chain); + + await Assert.That(success).IsFalse(); } /// - /// Tests that GetEventArgsTypeForEvent throws for invalid event. + /// Tests that TryGetAllValuesForPropertyChain gets all values in chain. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetEventArgsTypeForEvent_InvalidEvent_Throws() + public async Task TryGetAllValuesForPropertyChain_ValidChain_ReturnsAllValues() { - await Assert.That(() => Reflection.GetEventArgsTypeForEvent(typeof(TestClassWithEvent), "NonExistentEvent")) - .Throws(); + var fixture = new HostTestFixture { Child = new TestFixture { IsOnlyOneWord = "Test" } }; + Expression> expression = x => x.Child!.IsOnlyOneWord; + var chain = expression.Body.GetExpressionChain(); + + var success = Reflection.TryGetAllValuesForPropertyChain(out var values, fixture, chain); + + await Assert.That(success).IsTrue(); + await Assert.That(values).Count().IsEqualTo(2); + await Assert.That(values[0].Sender).IsEqualTo(fixture); + await Assert.That(values[1].Value).IsEqualTo("Test"); } /// - /// Tests that GetEventArgsTypeForEvent throws for null type. + /// Tests that TryGetValueForPropertyChain gets value from nested property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetEventArgsTypeForEvent_NullType_Throws() + public async Task TryGetValueForPropertyChain_NestedProperty_ReturnsValue() { - await Assert.That(() => Reflection.GetEventArgsTypeForEvent(null!, "TestEvent")) - .Throws(); + var fixture = new HostTestFixture { Child = new TestFixture { IsOnlyOneWord = "NestedTest" } }; + Expression> expression = x => x.Child!.IsOnlyOneWord; + var chain = expression.Body.GetExpressionChain(); + + var success = Reflection.TryGetValueForPropertyChain(out var value, fixture, chain); + + await Assert.That(success).IsTrue(); + await Assert.That(value).IsEqualTo("NestedTest"); } /// - /// Tests that ThrowIfMethodsNotOverloaded passes for overloaded methods. + /// Tests that TryGetValueForPropertyChain returns false when null in chain. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ThrowIfMethodsNotOverloaded_OverloadedMethods_DoesNotThrow() + public async Task TryGetValueForPropertyChain_NullInChain_ReturnsFalse() { - var target = new TestClassWithOverriddenMethods(); + var fixture = new HostTestFixture { Child = null }; + Expression> expression = x => x.Child!.IsOnlyOneWord; + var chain = expression.Body.GetExpressionChain(); - Reflection.ThrowIfMethodsNotOverloaded("TestCaller", target, nameof(TestClassWithOverriddenMethods.TestMethod)); + var success = Reflection.TryGetValueForPropertyChain(out var value, fixture, chain); - // If we got here without throwing, the test passes - await Task.CompletedTask; + await Assert.That(success).IsFalse(); + await Assert.That(value).IsNull(); } /// - /// Tests that ThrowIfMethodsNotOverloaded throws for missing methods. + /// Tests that TryGetValueForPropertyChain gets value from simple property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ThrowIfMethodsNotOverloaded_MissingMethod_Throws() + public async Task TryGetValueForPropertyChain_SimpleProperty_ReturnsValue() { - var target = new TestClassWithOverriddenMethods(); + var fixture = new TestFixture { IsOnlyOneWord = "Test" }; + Expression> expression = x => x.IsOnlyOneWord; + var chain = expression.Body.GetExpressionChain(); - await Assert.That(() => Reflection.ThrowIfMethodsNotOverloaded("TestCaller", target, "NonExistentMethod")) - .Throws(); + var success = Reflection.TryGetValueForPropertyChain(out var value, fixture, chain); + + await Assert.That(success).IsTrue(); + await Assert.That(value).IsEqualTo("Test"); } /// - /// Tests that IsStatic returns true for static property. + /// Tests that TrySetValueToPropertyChain sets value on nested property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task IsStatic_StaticProperty_ReturnsTrue() + public async Task TrySetValueToPropertyChain_NestedProperty_SetsValue() { - var propertyInfo = typeof(TestClassWithStaticProperty).GetProperty(nameof(TestClassWithStaticProperty.StaticProperty))!; + var fixture = new HostTestFixture { Child = new TestFixture() }; + Expression> expression = x => x.Child!.IsOnlyOneWord; + var chain = expression.Body.GetExpressionChain(); - var result = propertyInfo.IsStatic(); + var success = Reflection.TrySetValueToPropertyChain(fixture, chain, "NestedValue"); - await Assert.That(result).IsTrue(); + await Assert.That(success).IsTrue(); + await Assert.That(fixture.Child.IsOnlyOneWord).IsEqualTo("NestedValue"); } /// - /// Tests that IsStatic returns false for instance property. + /// Tests that TrySetValueToPropertyChain returns false when null in chain. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task IsStatic_InstanceProperty_ReturnsFalse() + public async Task TrySetValueToPropertyChain_NullInChain_ReturnsFalse() { - var propertyInfo = typeof(TestClassWithStaticProperty).GetProperty(nameof(TestClassWithStaticProperty.InstanceProperty))!; + var fixture = new HostTestFixture { Child = null }; + Expression> expression = x => x.Child!.IsOnlyOneWord; + var chain = expression.Body.GetExpressionChain(); - var result = propertyInfo.IsStatic(); + var success = Reflection.TrySetValueToPropertyChain(fixture, chain, "Value", false); - await Assert.That(result).IsFalse(); + await Assert.That(success).IsFalse(); } /// - /// Tests that IsStatic throws for null property. + /// Tests that TrySetValueToPropertyChain throws when shouldThrow is true and target is null. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task IsStatic_NullProperty_Throws() + public async Task TrySetValueToPropertyChain_NullTargetWithThrow_Throws() { - PropertyInfo propertyInfo = null!; + Expression> expression = x => x.Child!.IsOnlyOneWord; + var chain = expression.Body.GetExpressionChain(); - await Assert.That(() => propertyInfo.IsStatic()) + await Assert.That(() => Reflection.TrySetValueToPropertyChain(null, chain, "Value")) .Throws(); } /// - /// Tests that Rewrite simplifies expression. + /// Tests that TrySetValueToPropertyChain sets value on simple property. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task Rewrite_Expression_SimplifiesExpression() + public async Task TrySetValueToPropertyChain_SimpleProperty_SetsValue() { + var fixture = new TestFixture(); Expression> expression = x => x.IsOnlyOneWord; + var chain = expression.Body.GetExpressionChain(); - var result = Reflection.Rewrite(expression.Body); + var success = Reflection.TrySetValueToPropertyChain(fixture, chain, "NewValue"); - await Assert.That(result).IsNotNull(); + await Assert.That(success).IsTrue(); + await Assert.That(fixture.IsOnlyOneWord).IsEqualTo("NewValue"); } /// - /// Test class with a field. + /// Test class with an event. /// - private class TestClassWithField + private class TestClassWithEvent { /// - /// A test field. + /// A test event. /// - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Needed for test")] - public string? TestField; + public event EventHandler? TestEvent; + + /// + /// Raises the test event. + /// + protected virtual void OnTestEvent() => TestEvent?.Invoke(this, EventArgs.Empty); } /// - /// Test class with an event. + /// Test class with a field. /// - private class TestClassWithEvent + private class TestClassWithField { /// - /// A test event. + /// A test field. /// - public event EventHandler? TestEvent; - - /// - /// Raises the test event. - /// - protected virtual void OnTestEvent() => TestEvent?.Invoke(this, EventArgs.Empty); + [SuppressMessage( + "StyleCop.CSharp.MaintainabilityRules", + "SA1401:Fields should be private", + Justification = "Needed for test")] + public string? TestField; } /// - /// Test class with overridden methods. + /// Test class with overridden methods. /// private class TestClassWithOverriddenMethods { /// - /// A test method. + /// A test method. /// [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Needed for test")] public void TestMethod() @@ -577,17 +569,17 @@ public void TestMethod() } /// - /// Test class with static property. + /// Test class with static property. /// private class TestClassWithStaticProperty { /// - /// Gets or sets a static property. + /// Gets or sets a static property. /// public static string? StaticProperty { get; set; } /// - /// Gets or sets an instance property. + /// Gets or sets an instance property. /// public string? InstanceProperty { get; set; } } diff --git a/src/tests/ReactiveUI.Tests/Resolvers/DependencyResolverTests.cs b/src/tests/ReactiveUI.Tests/Resolvers/DependencyResolverTests.cs deleted file mode 100644 index d23a3366d6..0000000000 --- a/src/tests/ReactiveUI.Tests/Resolvers/DependencyResolverTests.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) 2025 .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; - -public sealed class DependencyResolverTests -{ - private static readonly RegistrationNamespace[][] NamespacesToRegister = - [ - [RegistrationNamespace.Winforms], - [RegistrationNamespace.Wpf], - [RegistrationNamespace.Uno], - [RegistrationNamespace.Blazor], - [RegistrationNamespace.Drawing], - [RegistrationNamespace.Blazor, RegistrationNamespace.Wpf] - ]; - - [Test] - public async Task AllDefaultServicesShouldBeRegistered() - { - var resolver = GenerateResolver(); - using (resolver.WithResolver()) - { - foreach (var shouldRegistered in GetServicesThatShouldBeRegistered(PlatformRegistrationManager.DefaultRegistrationNamespaces)) - { - var resolvedServices = resolver.GetServices(shouldRegistered.Key); - await Assert.That(resolvedServices.Count()).IsEqualTo(shouldRegistered.Value.Count); - foreach (var implementationType in shouldRegistered.Value) - { - await Assert.That(resolvedServices.Any(rs => rs.GetType() == implementationType)).IsTrue(); - } - } - } - } - - [Test] - public async Task AllDefaultServicesShouldBeRegisteredPerRegistrationNamespace() - { - foreach (var namespaces in NamespacesToRegister) - { - var resolver = GenerateResolver(); - using (resolver.WithResolver()) - { - resolver.InitializeReactiveUI(namespaces); - - foreach (var shouldRegistered in GetServicesThatShouldBeRegistered(namespaces)) - { - var resolvedServices = resolver.GetServices(shouldRegistered.Key); - - foreach (var implementationType in shouldRegistered.Value) - { - await Assert.That(resolvedServices.Any(rs => rs.GetType() == implementationType)).IsTrue(); - } - } - } - } - } - - [Test] - public async Task RegisteredNamespacesShouldBeRegistered() - { - foreach (var namespacesToRegister in NamespacesToRegister) - { - var resolver = GenerateResolver(); - using (resolver.WithResolver()) - { - var namespaces = namespacesToRegister; - - resolver.InitializeReactiveUI(namespaces); - - foreach (var shouldRegistered in GetServicesThatShouldBeRegistered(namespaces)) - { - var resolvedServices = resolver.GetServices(shouldRegistered.Key); - - await Assert.That(resolvedServices - .Select(x => x.GetType()?.AssemblyQualifiedName ?? string.Empty) - .Any(registeredType => - !string.IsNullOrEmpty(registeredType) && - PlatformRegistrationManager.DefaultRegistrationNamespaces - .Except(namespacesToRegister) - .All(x => !registeredType.Contains(x.ToString())))).IsTrue(); - } - } - } - } - - private static IEnumerable GetServiceRegistrationTypeNames( - IEnumerable registrationNamespaces) - { - foreach (var registrationNamespace in registrationNamespaces) - { - if (registrationNamespace == RegistrationNamespace.Wpf) - { - yield return "ReactiveUI.Wpf.Registrations, ReactiveUI.Wpf"; - } - - if (registrationNamespace == RegistrationNamespace.Winforms) - { - yield return "ReactiveUI.Winforms.Registrations, ReactiveUI.Winforms"; - } - } - } - - private static Dictionary> GetServicesThatShouldBeRegistered(IReadOnlyList onlyNamespaces) - { - var serviceTypeToImplementationTypes = new Dictionary>(); - - new Registrations().Register((factory, serviceType) => - { - if (!serviceTypeToImplementationTypes.TryGetValue(serviceType!, out var implementationTypes)) - { - implementationTypes = []; - serviceTypeToImplementationTypes.Add(serviceType!, implementationTypes); - } - - implementationTypes.Add(factory()!.GetType()); - }); - - new PlatformRegistrations().Register((factory, serviceType) => - { - if (!serviceTypeToImplementationTypes.TryGetValue(serviceType, out var implementationTypes)) - { - implementationTypes = []; - serviceTypeToImplementationTypes.Add(serviceType, implementationTypes); - } - - implementationTypes.Add(factory().GetType()); - }); - - var typeNames = GetServiceRegistrationTypeNames(onlyNamespaces); - - typeNames.ForEach(typeName => GetRegistrationsForPlatform(typeName, serviceTypeToImplementationTypes)); - - return serviceTypeToImplementationTypes; - } - - private static ModernDependencyResolver GenerateResolver() - { - var resolver = new ModernDependencyResolver(); - resolver.InitializeSplat(); - resolver.InitializeReactiveUI(); - return resolver; - } - - private static void GetRegistrationsForPlatform(string typeName, Dictionary> serviceTypeToImplementationTypes) - { - var platformRegistrationsType = Type.GetType(typeName); - if (platformRegistrationsType is not null) - { - var platformRegistrations = Activator.CreateInstance(platformRegistrationsType); - var register = platformRegistrationsType.GetMethod("Register"); - var registerParameter = new Action, Type>((factory, serviceType) => - { - if (!serviceTypeToImplementationTypes.TryGetValue(serviceType, out var implementationTypes)) - { - implementationTypes = []; - serviceTypeToImplementationTypes.Add(serviceType, implementationTypes); - } - - implementationTypes.Add(factory().GetType()); - }); - - register?.Invoke( - platformRegistrations, - [registerParameter]); - } - } -} diff --git a/src/tests/ReactiveUI.Tests/Resolvers/INPCObservableForPropertyTests.cs b/src/tests/ReactiveUI.Tests/Resolvers/INPCObservableForPropertyTests.cs index c99f43cee5..73150591b4 100644 --- a/src/tests/ReactiveUI.Tests/Resolvers/INPCObservableForPropertyTests.cs +++ b/src/tests/ReactiveUI.Tests/Resolvers/INPCObservableForPropertyTests.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Resolvers; public class INPCObservableForPropertyTests { @@ -16,12 +16,15 @@ public async Task CheckGetAffinityForObjectValues() using (Assert.Multiple()) { - await Assert.That(instance.GetAffinityForObject(typeof(TestClassChanged), string.Empty, false)).IsEqualTo(5); + await Assert.That(instance.GetAffinityForObject(typeof(TestClassChanged), string.Empty, false)) + .IsEqualTo(5); await Assert.That(instance.GetAffinityForObject(typeof(TestClassChanged), string.Empty, true)).IsEqualTo(0); await Assert.That(instance.GetAffinityForObject(typeof(object), string.Empty, false)).IsEqualTo(0); - await Assert.That(instance.GetAffinityForObject(typeof(TestClassChanging), string.Empty, true)).IsEqualTo(5); - await Assert.That(instance.GetAffinityForObject(typeof(TestClassChanging), string.Empty, false)).IsEqualTo(0); + await Assert.That(instance.GetAffinityForObject(typeof(TestClassChanging), string.Empty, true)) + .IsEqualTo(5); + await Assert.That(instance.GetAffinityForObject(typeof(TestClassChanging), string.Empty, false)) + .IsEqualTo(0); } await Assert.That(instance.GetAffinityForObject(typeof(object), string.Empty, false)).IsEqualTo(0); @@ -39,7 +42,8 @@ public async Task NotificationOnPropertyChanged() var changes = new List>(); - var propertyName = exp.GetMemberInfo()?.Name ?? throw new InvalidOperationException("propertyName should not be null"); + var propertyName = exp.GetMemberInfo()?.Name ?? + throw new InvalidOperationException("propertyName should not be null"); instance.GetNotificationForProperty(testClass, exp, propertyName).WhereNotNull().Subscribe(c => changes.Add(c)); testClass.Property1 = "test1"; @@ -66,8 +70,10 @@ public async Task NotificationOnPropertyChanging() var changes = new List>(); - var propertyName = exp.GetMemberInfo()?.Name ?? throw new InvalidOperationException("propertyName should not be null"); - instance.GetNotificationForProperty(testClass, exp, propertyName, true).WhereNotNull().Subscribe(c => changes.Add(c)); + var propertyName = exp.GetMemberInfo()?.Name ?? + throw new InvalidOperationException("propertyName should not be null"); + instance.GetNotificationForProperty(testClass, exp, propertyName, true).WhereNotNull() + .Subscribe(c => changes.Add(c)); testClass.Property1 = "test1"; testClass.Property1 = "test2"; @@ -93,8 +99,9 @@ public async Task NotificationOnWholeObjectChanged() var changes = new List>(); - var propertyName = exp.GetMemberInfo()?.Name ?? throw new InvalidOperationException("propertyName should not be null"); - instance.GetNotificationForProperty(testClass, exp, propertyName, false).WhereNotNull().Subscribe(c => changes.Add(c)); + var propertyName = exp.GetMemberInfo()?.Name ?? + throw new InvalidOperationException("propertyName should not be null"); + instance.GetNotificationForProperty(testClass, exp, propertyName).WhereNotNull().Subscribe(c => changes.Add(c)); testClass.OnPropertyChanged(null); testClass.OnPropertyChanged(string.Empty); @@ -120,8 +127,10 @@ public async Task NotificationOnWholeObjectChanging() var changes = new List>(); - var propertyName = exp.GetMemberInfo()?.Name ?? throw new InvalidOperationException("propertyName should not be null"); - instance.GetNotificationForProperty(testClass, exp, propertyName, true).WhereNotNull().Subscribe(c => changes.Add(c)); + var propertyName = exp.GetMemberInfo()?.Name ?? + throw new InvalidOperationException("propertyName should not be null"); + instance.GetNotificationForProperty(testClass, exp, propertyName, true).WhereNotNull() + .Subscribe(c => changes.Add(c)); testClass.OnPropertyChanging(null); testClass.OnPropertyChanging(string.Empty); @@ -163,7 +172,8 @@ public string? Property2 } } - public void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + public void OnPropertyChanged([CallerMemberName] string? propertyName = null) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private class TestClassChanging : INotifyPropertyChanging diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Resolvers/PocoObservableForPropertyTests.cs b/src/tests/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs similarity index 59% rename from src/tests/ReactiveUI.NonParallel.Tests/Resolvers/PocoObservableForPropertyTests.cs rename to src/tests/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs index 1562572187..a0881314a3 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Resolvers/PocoObservableForPropertyTests.cs +++ b/src/tests/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs @@ -3,50 +3,96 @@ // 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.Infrastructure.StaticState; +using ReactiveUI.Builder; -namespace ReactiveUI.Tests.Core; +namespace ReactiveUI.Tests.Resolvers; [NotInParallel] -public class PocoObservableForPropertyTests : IDisposable +[TestExecutor] +public class PocoObservableForPropertyTests { - private RxAppSchedulersScope? _schedulersScope; - - [Before(Test)] - public void SetUp() + [Test] + public async Task CheckGetAffinityForObjectValues() { - _schedulersScope = new RxAppSchedulersScope(); + var instance = new POCOObservableForProperty(); + + using (Assert.Multiple()) + { + await Assert.That( + instance.GetAffinityForObject( + typeof(PocoType), + nameof(PocoType.Property1))).IsEqualTo(1); + await Assert.That( + instance.GetAffinityForObject( + typeof(INPCClass), + "SomeProperty")).IsEqualTo(1); + } } - [After(Test)] - public void TearDown() + [Test] + [TestExecutor] + public async Task GetNotificationForPropertyNeverCompletes() { - _schedulersScope?.Dispose(); + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var instance = new POCOObservableForProperty(); + var poco = new PocoType { Property1 = "Test" }; + Expression> expr = x => x.Property1; + + var observable = instance.GetNotificationForProperty( + poco, + expr.Body, + nameof(PocoType.Property1), + false, + true); + + // Take 2 items - should only get 1 since POCO doesn't change + var results = new List>(); + var completed = false; + + observable + .Take(TimeSpan.FromMilliseconds(100), scheduler) + .Subscribe( + results.Add, + () => completed = true); + + // Advance virtual time to trigger the Take timeout + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(150)); + + // Should have received exactly 1 item (the initial value) and completed + using (Assert.Multiple()) + { + await Assert.That(results).Count().IsEqualTo(1); + await Assert.That(completed).IsTrue(); + } } [Test] - public async Task CheckGetAffinityForObjectValues() + public async Task GetNotificationForPropertyOnlyWarnsOnce() { - RxApp.EnsureInitialized(); var instance = new POCOObservableForProperty(); + var poco1 = new PocoType { Property1 = "Test1" }; + var poco2 = new PocoType { Property1 = "Test2" }; + Expression> expr = x => x.Property1; + + // First call should trigger warning (but we're suppressing it with suppressWarnings: false for testing) + var observable1 = instance.GetNotificationForProperty(poco1, expr.Body, nameof(PocoType.Property1)); + var result1 = await observable1.FirstAsync(); + + // Second call with different instance but same type and property should not warn again + var observable2 = instance.GetNotificationForProperty(poco2, expr.Body, nameof(PocoType.Property1)); + var result2 = await observable2.FirstAsync(); using (Assert.Multiple()) { - await Assert.That(instance.GetAffinityForObject( - typeof(PocoType), - null!, - false)).IsEqualTo(1); - await Assert.That(instance.GetAffinityForObject( - typeof(INPCClass), - null!, - false)).IsEqualTo(1); + await Assert.That(result1).IsNotNull(); + await Assert.That(result2).IsNotNull(); } } [Test] public async Task GetNotificationForPropertyReturnsObservable() { - RxApp.EnsureInitialized(); var instance = new POCOObservableForProperty(); var poco = new PocoType { Property1 = "Test" }; Expression> expr = x => x.Property1; @@ -59,7 +105,6 @@ public async Task GetNotificationForPropertyReturnsObservable() [Test] public async Task GetNotificationForPropertyReturnsSingleValue() { - RxApp.EnsureInitialized(); var instance = new POCOObservableForProperty(); var poco = new PocoType { Property1 = "Test" }; Expression> expr = x => x.Property1; @@ -74,24 +119,9 @@ public async Task GetNotificationForPropertyReturnsSingleValue() } } - [Test] - public async Task GetNotificationForPropertyWithBeforeChangedParameter() - { - RxApp.EnsureInitialized(); - var instance = new POCOObservableForProperty(); - var poco = new PocoType { Property1 = "Test" }; - Expression> expr = x => x.Property1; - - var observable = instance.GetNotificationForProperty(poco, expr.Body, nameof(PocoType.Property1), true, true); - var result = await observable.FirstAsync(); - - await Assert.That(result).IsNotNull(); - } - [Test] public void GetNotificationForPropertyThrowsOnNullSender() { - RxApp.EnsureInitialized(); var instance = new POCOObservableForProperty(); Expression> expr = x => x.Property1; @@ -100,40 +130,38 @@ public void GetNotificationForPropertyThrowsOnNullSender() } [Test] - public async Task GetNotificationForPropertyOnlyWarnsOnce() + public async Task GetNotificationForPropertyWithBeforeChangedParameter() { - RxApp.EnsureInitialized(); var instance = new POCOObservableForProperty(); - var poco1 = new PocoType { Property1 = "Test1" }; - var poco2 = new PocoType { Property1 = "Test2" }; + var poco = new PocoType { Property1 = "Test" }; Expression> expr = x => x.Property1; - // First call should trigger warning (but we're suppressing it with suppressWarnings: false for testing) - var observable1 = instance.GetNotificationForProperty(poco1, expr.Body, nameof(PocoType.Property1), false, false); - var result1 = await observable1.FirstAsync(); - - // Second call with different instance but same type and property should not warn again - var observable2 = instance.GetNotificationForProperty(poco2, expr.Body, nameof(PocoType.Property1), false, false); - var result2 = await observable2.FirstAsync(); + var observable = instance.GetNotificationForProperty(poco, expr.Body, nameof(PocoType.Property1), true, true); + var result = await observable.FirstAsync(); - using (Assert.Multiple()) - { - await Assert.That(result1).IsNotNull(); - await Assert.That(result2).IsNotNull(); - } + await Assert.That(result).IsNotNull(); } [Test] public async Task GetNotificationForPropertyWithDifferentProperties() { - RxApp.EnsureInitialized(); var instance = new POCOObservableForProperty(); var poco = new PocoType { Property1 = "Test1", Property2 = "Test2" }; Expression> expr1 = x => x.Property1; Expression> expr2 = x => x.Property2; - var observable1 = instance.GetNotificationForProperty(poco, expr1.Body, nameof(PocoType.Property1), false, true); - var observable2 = instance.GetNotificationForProperty(poco, expr2.Body, nameof(PocoType.Property2), false, true); + var observable1 = instance.GetNotificationForProperty( + poco, + expr1.Body, + nameof(PocoType.Property1), + false, + true); + var observable2 = instance.GetNotificationForProperty( + poco, + expr2.Body, + nameof(PocoType.Property2), + false, + true); var result1 = await observable1.FirstAsync(); var result2 = await observable2.FirstAsync(); @@ -147,53 +175,13 @@ public async Task GetNotificationForPropertyWithDifferentProperties() } } - [Test] - public async Task GetNotificationForPropertyNeverCompletes() + private class INPCClass : INotifyPropertyChanged { - RxApp.EnsureInitialized(); - var testScheduler = new Microsoft.Reactive.Testing.TestScheduler(); - var originalScheduler = RxApp.MainThreadScheduler; - - try - { - RxApp.MainThreadScheduler = testScheduler; - - var instance = new POCOObservableForProperty(); - var poco = new PocoType { Property1 = "Test" }; - Expression> expr = x => x.Property1; - - var observable = instance.GetNotificationForProperty(poco, expr.Body, nameof(PocoType.Property1), false, true); - - // Take 2 items - should only get 1 since POCO doesn't change - var results = new List>(); - var completed = false; - - observable - .Take(TimeSpan.FromMilliseconds(100), testScheduler) - .Subscribe( - results.Add, - onCompleted: () => completed = true); - - // Advance virtual time to trigger the Take timeout - testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(150).Ticks); - - // Should have received exactly 1 item (the initial value) and completed - using (Assert.Multiple()) - { - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(completed).IsTrue(); - } - } - finally - { - RxApp.MainThreadScheduler = originalScheduler; - } - } + public event PropertyChangedEventHandler? PropertyChanged; - public void Dispose() - { - _schedulersScope?.Dispose(); - _schedulersScope = null; + public void NotifyPropertyChanged() => PropertyChanged?.Invoke( + this, + new PropertyChangedEventArgs(string.Empty)); } private class PocoType @@ -202,13 +190,4 @@ private class PocoType public string? Property2 { get; set; } } - - private class INPCClass : INotifyPropertyChanged - { - public event PropertyChangedEventHandler? PropertyChanged; - - public void NotifyPropertyChanged() => PropertyChanged?.Invoke( - this, - new PropertyChangedEventArgs(string.Empty)); - } } diff --git a/src/tests/ReactiveUI.Tests/RxAppBuilderTest.cs b/src/tests/ReactiveUI.Tests/RxAppBuilderTest.cs index 80af180ca4..165ef8873b 100644 --- a/src/tests/ReactiveUI.Tests/RxAppBuilderTest.cs +++ b/src/tests/ReactiveUI.Tests/RxAppBuilderTest.cs @@ -8,14 +8,27 @@ namespace ReactiveUI.Tests; /// -/// Tests for . +/// Tests for . /// public class RxAppBuilderTest { /// - /// Tests that CreateReactiveUIBuilder returns a builder. + /// Tests that CreateReactiveUIBuilder throws for null resolver. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. + [Test] + public async Task CreateReactiveUIBuilder_NullResolver_Throws() + { + IMutableDependencyResolver resolver = null!; + + await Assert.That(() => resolver.CreateReactiveUIBuilder()) + .Throws(); + } + + /// + /// Tests that CreateReactiveUIBuilder returns a builder. + /// + /// A representing the asynchronous operation. [Test] public async Task CreateReactiveUIBuilder_ReturnsBuilder() { @@ -26,9 +39,9 @@ public async Task CreateReactiveUIBuilder_ReturnsBuilder() } /// - /// Tests that CreateReactiveUIBuilder with resolver returns a builder. + /// Tests that CreateReactiveUIBuilder with resolver returns a builder. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task CreateReactiveUIBuilder_WithResolver_ReturnsBuilder() { @@ -41,20 +54,7 @@ public async Task CreateReactiveUIBuilder_WithResolver_ReturnsBuilder() } /// - /// Tests that CreateReactiveUIBuilder throws for null resolver. - /// - /// A representing the asynchronous operation. - [Test] - public async Task CreateReactiveUIBuilder_NullResolver_Throws() - { - IMutableDependencyResolver resolver = null!; - - await Assert.That(() => resolver.CreateReactiveUIBuilder()) - .Throws(); - } - - /// - /// Test resolver for testing. + /// Test resolver for testing. /// private class TestResolver : IMutableDependencyResolver, IReadonlyDependencyResolver { @@ -110,11 +110,6 @@ public void Register(string? contract) { } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Interface implementation")] - public void RegisterConstant(object? value, Type? serviceType, string? contract) - { - } - public void RegisterConstant(T? value) where T : class { @@ -125,36 +120,44 @@ public void RegisterConstant(T? value, string? contract) { } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Interface implementation")] - public void RegisterLazySingleton(Func factory, Type? serviceType, string? contract) + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Interface implementation")] + public void RegisterConstant(object? value, Type? serviceType, string? contract) { } - public void RegisterLazySingleton<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>(Func factory) + public void RegisterLazySingleton< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + T>(Func factory) where T : class { } - public void RegisterLazySingleton<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>(Func factory, string? contract) + public void RegisterLazySingleton< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + T>( + Func factory, + string? contract) where T : class { } - public void UnregisterCurrent(Type? serviceType) + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Interface implementation")] + public void RegisterLazySingleton(Func factory, Type? serviceType, string? contract) { } - public void UnregisterCurrent(Type? serviceType, string? contract) - { - } + public IDisposable ServiceRegistrationCallback(Type serviceType, Action callback) => + Disposable.Empty; - public void UnregisterCurrent() - { - } + public IDisposable ServiceRegistrationCallback( + Type serviceType, + string? contract, + Action callback) => Disposable.Empty; - public void UnregisterCurrent(string? contract) - { - } + public IDisposable ServiceRegistrationCallback(Action callback) => Disposable.Empty; + + public IDisposable ServiceRegistrationCallback(string? contract, Action callback) => + Disposable.Empty; public void UnregisterAll(Type? serviceType) { @@ -172,12 +175,20 @@ public void UnregisterAll(string? contract) { } - public IDisposable ServiceRegistrationCallback(Type serviceType, Action callback) => Disposable.Empty; + public void UnregisterCurrent(Type? serviceType) + { + } - public IDisposable ServiceRegistrationCallback(Type serviceType, string? contract, Action callback) => Disposable.Empty; + public void UnregisterCurrent(Type? serviceType, string? contract) + { + } - public IDisposable ServiceRegistrationCallback(Action callback) => Disposable.Empty; + public void UnregisterCurrent() + { + } - public IDisposable ServiceRegistrationCallback(string? contract, Action callback) => Disposable.Empty; + public void UnregisterCurrent(string? contract) + { + } } } diff --git a/src/tests/ReactiveUI.NonParallel.Tests/RxSchedulersTest.cs b/src/tests/ReactiveUI.Tests/RxSchedulersTest.cs similarity index 71% rename from src/tests/ReactiveUI.NonParallel.Tests/RxSchedulersTest.cs rename to src/tests/ReactiveUI.Tests/RxSchedulersTest.cs index f8dcfdf266..98f45ed2ce 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/RxSchedulersTest.cs +++ b/src/tests/ReactiveUI.Tests/RxSchedulersTest.cs @@ -3,21 +3,22 @@ // 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.Reactive.Testing; +using ReactiveUI.Tests.Utilities.Schedulers; -namespace ReactiveUI.Tests.Core; +namespace ReactiveUI.Tests; /// -/// Tests the RxSchedulers class to ensure it works without RequiresUnreferencedCode attributes. +/// Tests the RxSchedulers class to ensure it works without RequiresUnreferencedCode attributes. /// [NotInParallel] public class RxSchedulersTest { /// - /// Tests that schedulers can be accessed without attributes. + /// Tests that schedulers can be accessed without attributes. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] + [TestExecutor] public async Task SchedulersCanBeAccessedWithoutAttributes() { // This test method itself should not require RequiresUnreferencedCode @@ -33,20 +34,21 @@ public async Task SchedulersCanBeAccessedWithoutAttributes() } /// - /// Tests that schedulers can be set and retrieved. + /// Tests that schedulers can be set and retrieved. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] + [TestExecutor] public async Task SchedulersCanBeSetAndRetrieved() { + var testScheduler = TestContext.Current.GetVirtualTimeScheduler(); + // Store original schedulers to ensure test isolation var originalMainScheduler = RxSchedulers.MainThreadScheduler; var originalTaskpoolScheduler = RxSchedulers.TaskpoolScheduler; try { - var testScheduler = new TestScheduler(); - // Set schedulers RxSchedulers.MainThreadScheduler = testScheduler; RxSchedulers.TaskpoolScheduler = testScheduler; @@ -67,10 +69,11 @@ public async Task SchedulersCanBeSetAndRetrieved() } /// - /// Tests that RxSchedulers provides basic scheduler functionality. + /// Tests that RxSchedulers provides basic scheduler functionality. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] + [TestExecutor] public async Task SchedulersProvideBasicFunctionality() { var mainScheduler = RxSchedulers.MainThreadScheduler; @@ -82,14 +85,14 @@ public async Task SchedulersProvideBasicFunctionality() await Assert.That(mainScheduler).IsAssignableTo(); await Assert.That(taskpoolScheduler).IsAssignableTo(); - // Verify they have Now property - only check if not using TestScheduler - // TestScheduler.Now returns DateTimeOffset.MinValue by design - if (mainScheduler is not TestScheduler) + // Verify they have Now property - only check if not using VirtualTimeScheduler + // VirtualTimeScheduler.Now returns DateTimeOffset.MinValue by design + if (mainScheduler is not VirtualTimeScheduler) { await Assert.That(mainScheduler.Now).IsGreaterThan(DateTimeOffset.MinValue); } - if (taskpoolScheduler is not TestScheduler) + if (taskpoolScheduler is not VirtualTimeScheduler) { await Assert.That(taskpoolScheduler.Now).IsGreaterThan(DateTimeOffset.MinValue); } diff --git a/src/tests/ReactiveUI.Tests/ScheduledSubjectTest.cs b/src/tests/ReactiveUI.Tests/ScheduledSubjectTest.cs index 06905a322f..e08598c750 100644 --- a/src/tests/ReactiveUI.Tests/ScheduledSubjectTest.cs +++ b/src/tests/ReactiveUI.Tests/ScheduledSubjectTest.cs @@ -3,44 +3,60 @@ // 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.Reactive.Testing; +using ReactiveUI.Tests.Utilities; namespace ReactiveUI.Tests; /// -/// Tests for . +/// Tests for . /// public class ScheduledSubjectTest { /// - /// Tests that OnNext emits values. + /// Tests that constructor with default observer sends values to it. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OnNext_EmitsValues() + public async Task Constructor_WithDefaultObserver_SendsValuesToIt() { - var scheduler = new TestScheduler(); - var subject = new ScheduledSubject(scheduler); + var scheduler = ImmediateScheduler.Instance; var results = new List(); + var defaultObserver = Observer.Create(results.Add); - subject.Subscribe(results.Add); + var subject = new ScheduledSubject(scheduler, defaultObserver); subject.OnNext(1); subject.OnNext(2); - scheduler.Start(); await Assert.That(results).Count().IsEqualTo(2); - await Assert.That(results[0]).IsEqualTo(1); - await Assert.That(results[1]).IsEqualTo(2); } /// - /// Tests that OnCompleted completes the observable. + /// Tests that Dispose cleans up resources. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] + + [TestExecutor] + public async Task Dispose_CleansUpResources() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + var subject = new ScheduledSubject(scheduler); + + subject.Dispose(); + + await Task.CompletedTask; + } + + /// + /// Tests that OnCompleted completes the observable. + /// + /// A representing the asynchronous operation. + [Test] + + [TestExecutor] public async Task OnCompleted_CompletesObservable() { - var scheduler = new TestScheduler(); + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); var subject = new ScheduledSubject(scheduler); var completed = false; @@ -52,13 +68,15 @@ public async Task OnCompleted_CompletesObservable() } /// - /// Tests that OnError sends error to observers. + /// Tests that OnError sends error to observers. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] + + [TestExecutor] public async Task OnError_SendsErrorToObservers() { - var scheduler = new TestScheduler(); + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); var subject = new ScheduledSubject(scheduler); Exception? receivedError = null; @@ -71,31 +89,38 @@ public async Task OnError_SendsErrorToObservers() } /// - /// Tests that constructor with default observer sends values to it. + /// Tests that OnNext emits values. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task Constructor_WithDefaultObserver_SendsValuesToIt() + + [TestExecutor] + public async Task OnNext_EmitsValues() { - var scheduler = ImmediateScheduler.Instance; + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + var subject = new ScheduledSubject(scheduler); var results = new List(); - var defaultObserver = Observer.Create(results.Add); - var subject = new ScheduledSubject(scheduler, defaultObserver); + subject.Subscribe(results.Add); subject.OnNext(1); subject.OnNext(2); + scheduler.Start(); await Assert.That(results).Count().IsEqualTo(2); + await Assert.That(results[0]).IsEqualTo(1); + await Assert.That(results[1]).IsEqualTo(2); } /// - /// Tests that Subscribe returns a disposable. + /// Tests that Subscribe returns a disposable. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] + + [TestExecutor] public async Task Subscribe_ReturnsDisposable() { - var scheduler = new TestScheduler(); + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); var subject = new ScheduledSubject(scheduler); var subscription = subject.Subscribe(_ => { }); @@ -105,47 +130,30 @@ public async Task Subscribe_ReturnsDisposable() } /// - /// Tests that Dispose cleans up resources. - /// - /// A representing the asynchronous operation. - [Test] - public async Task Dispose_CleansUpResources() - { - var scheduler = new TestScheduler(); - var subject = new ScheduledSubject(scheduler); - - subject.Dispose(); - - await Task.CompletedTask; - } - - /// - /// Tests that values are scheduled on the specified scheduler. + /// Tests that values are scheduled on the specified scheduler. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task Subscribe_SchedulesOnSpecifiedScheduler() { - var scheduler = new TestScheduler(); + var scheduler = new CountingTestScheduler(ImmediateScheduler.Instance); var subject = new ScheduledSubject(scheduler); var results = new List(); subject.Subscribe(results.Add); subject.OnNext(1); - // Before advancing scheduler, nothing should be received - await Assert.That(results).Count().IsEqualTo(0); - - scheduler.Start(); - - // After advancing scheduler, value should be received - await Assert.That(results).Count().IsEqualTo(1); + using (Assert.Multiple()) + { + await Assert.That(scheduler.ScheduledItems).Count().IsGreaterThan(0); + await Assert.That(results).Count().IsEqualTo(1); + } } /// - /// Tests that subscription disposal stops receiving values. + /// Tests that subscription disposal stops receiving values. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task Subscription_WhenDisposed_StopsReceivingValues() { diff --git a/src/tests/ReactiveUI.NonParallel.Tests/SchedulerConsumptionTest.cs b/src/tests/ReactiveUI.Tests/SchedulerConsumptionTest.cs similarity index 76% rename from src/tests/ReactiveUI.NonParallel.Tests/SchedulerConsumptionTest.cs rename to src/tests/ReactiveUI.Tests/SchedulerConsumptionTest.cs index c2420457ac..be39167ac1 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/SchedulerConsumptionTest.cs +++ b/src/tests/ReactiveUI.Tests/SchedulerConsumptionTest.cs @@ -3,50 +3,41 @@ // 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.Reactive.Testing; - -namespace ReactiveUI.Tests.Core; +namespace ReactiveUI.Tests; /// -/// Demonstrates using ReactiveUI schedulers without RequiresUnreferencedCode attributes. +/// Demonstrates using ReactiveUI schedulers without RequiresUnreferencedCode attributes. /// [NotInParallel] +[TestExecutor] public class SchedulerConsumptionTest { [Test] - public async Task ViewModelCanUseSchedulersWithoutAttributes() + public async Task ReactivePropertyFactoryMethodsWork() { - var testScheduler = new TestScheduler(); - var originalScheduler = RxSchedulers.MainThreadScheduler; + // These factory methods use RxSchedulers internally, so no RequiresUnreferencedCode needed + var prop1 = ReactiveProperty.Create(); + var prop2 = ReactiveProperty.Create("initial"); + var prop3 = ReactiveProperty.Create(42, false, true); - try + using (Assert.Multiple()) { - // Use test scheduler for predictable behavior - RxSchedulers.MainThreadScheduler = testScheduler; - - var viewModel = new ExampleViewModel - { - Name = "ReactiveUI" - }; - - // Advance the test scheduler to process the observable - testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(1).Ticks); + await Assert.That(prop1).IsNotNull(); + await Assert.That(prop2).IsNotNull(); + await Assert.That(prop3).IsNotNull(); - // For this test, we're primarily verifying that the code compiles and runs - // without requiring RequiresUnreferencedCode attributes on the test method - await Assert.That(viewModel.Name).IsEqualTo("ReactiveUI"); - } - finally - { - // Restore original scheduler - RxSchedulers.MainThreadScheduler = originalScheduler; + await Assert.That(prop1.Value).IsNull(); + await Assert.That(prop2.Value).IsEqualTo("initial"); + await Assert.That(prop3.Value).IsEqualTo(42); } } [Test] + + [TestExecutor] public async Task RepositoryCanUseSchedulersWithoutAttributes() { - var testScheduler = new TestScheduler(); + var testScheduler = TestContext.Current.GetVirtualTimeScheduler(); var originalScheduler = RxSchedulers.TaskpoolScheduler; try @@ -61,7 +52,7 @@ public async Task RepositoryCanUseSchedulersWithoutAttributes() repository.PublishData("test"); // Advance the test scheduler to process the observable - testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(1).Ticks); + testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); await Assert.That(result).IsEqualTo("Processed: test"); } @@ -73,44 +64,52 @@ public async Task RepositoryCanUseSchedulersWithoutAttributes() } [Test] - public async Task ReactivePropertyFactoryMethodsWork() + [TestExecutor] + public async Task ViewModelCanUseSchedulersWithoutAttributes() { - // These factory methods use RxSchedulers internally, so no RequiresUnreferencedCode needed - var prop1 = ReactiveProperty.Create(); - var prop2 = ReactiveProperty.Create("initial"); - var prop3 = ReactiveProperty.Create(42, false, true); + var testScheduler = TestContext.Current.GetVirtualTimeScheduler(); + var originalScheduler = RxSchedulers.MainThreadScheduler; - using (Assert.Multiple()) + try { - await Assert.That(prop1).IsNotNull(); - await Assert.That(prop2).IsNotNull(); - await Assert.That(prop3).IsNotNull(); + // Use test scheduler for predictable behavior + RxSchedulers.MainThreadScheduler = testScheduler; - await Assert.That(prop1.Value).IsNull(); - await Assert.That(prop2.Value).IsEqualTo("initial"); - await Assert.That(prop3.Value).IsEqualTo(42); + var viewModel = new ExampleViewModel { Name = "ReactiveUI" }; + + // Advance the test scheduler to process the observable + testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + // For this test, we're primarily verifying that the code compiles and runs + // without requiring RequiresUnreferencedCode attributes on the test method + await Assert.That(viewModel.Name).IsEqualTo("ReactiveUI"); + } + finally + { + // Restore original scheduler + RxSchedulers.MainThreadScheduler = originalScheduler; } } /// - /// Example repository class that uses RxSchedulers without requiring attributes. + /// Example repository class that uses RxSchedulers without requiring attributes. /// private sealed class ExampleRepository : IDisposable { private readonly Subject _dataSubject = new(); + public void Dispose() => _dataSubject?.Dispose(); + public IObservable GetData() => _dataSubject - .ObserveOn(RxSchedulers.TaskpoolScheduler) // No RequiresUnreferencedCode needed! - .Select(data => $"Processed: {data}"); + .ObserveOn(RxSchedulers.TaskpoolScheduler) // No RequiresUnreferencedCode needed! + .Select(data => $"Processed: {data}"); public void PublishData(string data) => _dataSubject.OnNext(data); - - public void Dispose() => _dataSubject?.Dispose(); } /// - /// Example ViewModel that uses RxSchedulers without requiring attributes. - /// This would previously require RequiresUnreferencedCode when using RxApp schedulers. + /// Example ViewModel that uses RxSchedulers without requiring attributes. + /// This would previously require RequiresUnreferencedCode when using RxApp schedulers. /// private class ExampleViewModel : ReactiveObject { @@ -118,16 +117,16 @@ private class ExampleViewModel : ReactiveObject private string? _name; public ExampleViewModel() => _greeting = this.WhenAnyValue(x => x.Name) - .Select(name => $"Hello, {name ?? "World"}!") - .ObserveOn(RxSchedulers.MainThreadScheduler) // No RequiresUnreferencedCode needed! - .ToProperty(this, nameof(Greeting), scheduler: RxSchedulers.MainThreadScheduler); + .Select(name => $"Hello, {name ?? "World"}!") + .ObserveOn(RxSchedulers.MainThreadScheduler) // No RequiresUnreferencedCode needed! + .ToProperty(this, nameof(Greeting), scheduler: RxSchedulers.MainThreadScheduler); + + public string Greeting => _greeting.Value; public string? Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); } - - public string Greeting => _greeting.Value; } } diff --git a/src/tests/ReactiveUI.Tests/Suspension/DummySuspensionDriverTests.cs b/src/tests/ReactiveUI.Tests/Suspension/DummySuspensionDriverTests.cs new file mode 100644 index 0000000000..384e837a7b --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Suspension/DummySuspensionDriverTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) 2025 .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.Suspension; + +/// +/// Tests for DummySuspensionDriver. +/// +public class DummySuspensionDriverTests +{ + /// + /// Tests that DummySuspensionDriver InvalidateState returns observable. + /// + /// A representing the asynchronous operation. + [Test] + public async Task DummySuspensionDriver_InvalidateState_ReturnsObservable() + { + // Arrange + var driver = new DummySuspensionDriver(); + + // Act + var result = driver.InvalidateState(); + + // Assert + await Assert.That(result).IsNotNull(); + } + + /// + /// Tests that DummySuspensionDriver LoadState returns observable. + /// + /// A representing the asynchronous operation. + [Test] + public async Task DummySuspensionDriver_LoadState_ReturnsObservable() + { + // Arrange + var driver = new DummySuspensionDriver(); + + // Act + var result = driver.LoadState(); + + // Assert + await Assert.That(result).IsNotNull(); + } + + /// + /// Tests that DummySuspensionDriver SaveState handles null state. + /// + /// A representing the asynchronous operation. + [Test] + public async Task DummySuspensionDriver_SaveState_NullState_ReturnsObservable() + { + // Arrange + var driver = new DummySuspensionDriver(); + + // Act + var result = driver.SaveState(null!); + + // Assert + await Assert.That(result).IsNotNull(); + } + + /// + /// Tests that DummySuspensionDriver SaveState returns observable. + /// + /// A representing the asynchronous operation. + [Test] + public async Task DummySuspensionDriver_SaveState_ReturnsObservable() + { + // Arrange + var driver = new DummySuspensionDriver(); + var state = new { TestProperty = "test" }; + + // Act + var result = driver.SaveState(state); + + // Assert + await Assert.That(result).IsNotNull(); + } +} diff --git a/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostTests.cs b/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostTests.cs index 3aad3cc7ba..5eabfc9143 100644 --- a/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostTests.cs +++ b/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostTests.cs @@ -6,152 +6,152 @@ namespace ReactiveUI.Tests.Suspension; /// -/// Tests for SuspensionHost. +/// Tests for SuspensionHost. /// public class SuspensionHostTests { [Test] - public async Task IsLaunchingNew_SetAndGet_ReturnsCorrectObservable() + public async Task AppState_PropertyChanged_RaisesNotification() { using var host = new SuspensionHost(); - var wasTriggered = false; + var propertyChanged = false; - host.IsLaunchingNew = Observable.Return(Unit.Default, ImmediateScheduler.Instance); + host.PropertyChanged += (sender, args) => + { + if (args.PropertyName == nameof(host.AppState)) + { + propertyChanged = true; + } + }; - using var subscription = host.IsLaunchingNew.Subscribe(_ => wasTriggered = true); + host.AppState = new DummyAppState(); - await Assert.That(wasTriggered).IsTrue(); + await Assert.That(propertyChanged).IsTrue(); } [Test] - public async Task IsResuming_SetAndGet_ReturnsCorrectObservable() + public async Task AppState_SetAndGet_ReturnsCorrectValue() { using var host = new SuspensionHost(); - var wasTriggered = false; - - host.IsResuming = Observable.Return(Unit.Default, ImmediateScheduler.Instance); + var state = new DummyAppState(); - using var subscription = host.IsResuming.Subscribe(_ => wasTriggered = true); + host.AppState = state; - await Assert.That(wasTriggered).IsTrue(); + await Assert.That(host.AppState).IsSameReferenceAs(state); } [Test] - public async Task IsUnpausing_SetAndGet_ReturnsCorrectObservable() + public async Task Constructor_DefaultObservables_ThrowExceptionOnSubscribe() { using var host = new SuspensionHost(); - var wasTriggered = false; - - host.IsUnpausing = Observable.Return(Unit.Default, ImmediateScheduler.Instance); + var gotError = false; - using var subscription = host.IsUnpausing.Subscribe(_ => wasTriggered = true); + host.IsLaunchingNew.Subscribe(_ => { }, ex => gotError = true); - await Assert.That(wasTriggered).IsTrue(); + await Assert.That(gotError).IsTrue(); } [Test] - public async Task ShouldPersistState_SetAndGet_ReturnsCorrectObservable() + public async Task CreateNewAppState_SetAndGet_ReturnsCorrectFunc() { using var host = new SuspensionHost(); - var wasTriggered = false; - var disposable = Disposable.Empty; - - host.ShouldPersistState = Observable.Return(disposable, ImmediateScheduler.Instance); - - using var subscription = host.ShouldPersistState.Subscribe(_ => wasTriggered = true); + host.CreateNewAppState = () => new DummyAppState(); - await Assert.That(wasTriggered).IsTrue(); + await Assert.That(host.CreateNewAppState).IsNotNull(); + await Assert.That(host.CreateNewAppState!()).IsTypeOf(); } [Test] - public async Task ShouldInvalidateState_SetAndGet_ReturnsCorrectObservable() + public async Task Dispose_CalledMultipleTimes_DoesNotThrow() { - using var host = new SuspensionHost(); - var wasTriggered = false; - - host.ShouldInvalidateState = Observable.Return(Unit.Default, ImmediateScheduler.Instance); + var host = new SuspensionHost(); - using var subscription = host.ShouldInvalidateState.Subscribe(_ => wasTriggered = true); + host.Dispose(); - await Assert.That(wasTriggered).IsTrue(); + await Assert.That(() => host.Dispose()).ThrowsNothing(); } [Test] - public async Task AppState_SetAndGet_ReturnsCorrectValue() + public async Task Dispose_DisposesAllSubjects() { - using var host = new SuspensionHost(); - var state = new DummyAppState(); + var host = new SuspensionHost(); - host.AppState = state; + // Set observables before disposal + host.IsLaunchingNew = Observable.Return(Unit.Default, ImmediateScheduler.Instance); + host.IsResuming = Observable.Return(Unit.Default, ImmediateScheduler.Instance); + host.IsUnpausing = Observable.Return(Unit.Default, ImmediateScheduler.Instance); + host.ShouldPersistState = Observable.Return(Disposable.Empty, ImmediateScheduler.Instance); + host.ShouldInvalidateState = Observable.Return(Unit.Default, ImmediateScheduler.Instance); - await Assert.That(host.AppState).IsSameReferenceAs(state); + host.Dispose(); + + // Verify disposal occurred + await Assert.That(host).IsNotNull(); } [Test] - public async Task AppState_PropertyChanged_RaisesNotification() + public async Task IsLaunchingNew_SetAndGet_ReturnsCorrectObservable() { using var host = new SuspensionHost(); - var propertyChanged = false; + var wasTriggered = false; - host.PropertyChanged += (sender, args) => - { - if (args.PropertyName == nameof(host.AppState)) - { - propertyChanged = true; - } - }; + host.IsLaunchingNew = Observable.Return(Unit.Default, ImmediateScheduler.Instance); - host.AppState = new DummyAppState(); + using var subscription = host.IsLaunchingNew.Subscribe(_ => wasTriggered = true); - await Assert.That(propertyChanged).IsTrue(); + await Assert.That(wasTriggered).IsTrue(); } [Test] - public async Task CreateNewAppState_SetAndGet_ReturnsCorrectFunc() + public async Task IsResuming_SetAndGet_ReturnsCorrectObservable() { using var host = new SuspensionHost(); - host.CreateNewAppState = () => new DummyAppState(); + var wasTriggered = false; - await Assert.That(host.CreateNewAppState).IsNotNull(); - await Assert.That(host.CreateNewAppState!()).IsTypeOf(); + host.IsResuming = Observable.Return(Unit.Default, ImmediateScheduler.Instance); + + using var subscription = host.IsResuming.Subscribe(_ => wasTriggered = true); + + await Assert.That(wasTriggered).IsTrue(); } [Test] - public async Task Constructor_DefaultObservables_ThrowExceptionOnSubscribe() + public async Task IsUnpausing_SetAndGet_ReturnsCorrectObservable() { using var host = new SuspensionHost(); - var gotError = false; + var wasTriggered = false; - host.IsLaunchingNew.Subscribe(_ => { }, ex => gotError = true); + host.IsUnpausing = Observable.Return(Unit.Default, ImmediateScheduler.Instance); - await Assert.That(gotError).IsTrue(); + using var subscription = host.IsUnpausing.Subscribe(_ => wasTriggered = true); + + await Assert.That(wasTriggered).IsTrue(); } [Test] - public async Task Dispose_DisposesAllSubjects() + public async Task ShouldInvalidateState_SetAndGet_ReturnsCorrectObservable() { - var host = new SuspensionHost(); + using var host = new SuspensionHost(); + var wasTriggered = false; - // Set observables before disposal - host.IsLaunchingNew = Observable.Return(Unit.Default, ImmediateScheduler.Instance); - host.IsResuming = Observable.Return(Unit.Default, ImmediateScheduler.Instance); - host.IsUnpausing = Observable.Return(Unit.Default, ImmediateScheduler.Instance); - host.ShouldPersistState = Observable.Return(Disposable.Empty, ImmediateScheduler.Instance); host.ShouldInvalidateState = Observable.Return(Unit.Default, ImmediateScheduler.Instance); - host.Dispose(); + using var subscription = host.ShouldInvalidateState.Subscribe(_ => wasTriggered = true); - // Verify disposal occurred - await Assert.That(host).IsNotNull(); + await Assert.That(wasTriggered).IsTrue(); } [Test] - public async Task Dispose_CalledMultipleTimes_DoesNotThrow() + public async Task ShouldPersistState_SetAndGet_ReturnsCorrectObservable() { - var host = new SuspensionHost(); + using var host = new SuspensionHost(); + var wasTriggered = false; + var disposable = Disposable.Empty; - host.Dispose(); + host.ShouldPersistState = Observable.Return(disposable, ImmediateScheduler.Instance); - await Assert.That(() => host.Dispose()).ThrowsNothing(); + using var subscription = host.ShouldPersistState.Subscribe(_ => wasTriggered = true); + + await Assert.That(wasTriggered).IsTrue(); } } diff --git a/src/tests/ReactiveUI.NonParallel.Tests/SuspensionHostExtensionsTests.cs b/src/tests/ReactiveUI.Tests/SuspensionHostExtensionsTests.cs similarity index 74% rename from src/tests/ReactiveUI.NonParallel.Tests/SuspensionHostExtensionsTests.cs rename to src/tests/ReactiveUI.Tests/SuspensionHostExtensionsTests.cs index 802b1e0ba8..3272da1fe5 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/SuspensionHostExtensionsTests.cs +++ b/src/tests/ReactiveUI.Tests/SuspensionHostExtensionsTests.cs @@ -3,159 +3,105 @@ // 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.Text.Json.Serialization.Metadata; + namespace ReactiveUI.Tests; /// -/// Tests for SuspensionHostExtensions that use static state. -/// These tests must run in NonParallel suite due to static fields in SuspensionHostExtensions. +/// Tests for SuspensionHostExtensions that use static state. +/// These tests must run in NonParallel suite due to static fields in SuspensionHostExtensions. /// public class SuspensionHostExtensionsTests { private Func>? _previousEnsureLoadAppStateFunc; private ISuspensionDriver? _previousSuspensionDriver; - /// - /// Saves the static state before each test. - /// - [Before(Test)] - public void SaveStaticState() - { - _previousEnsureLoadAppStateFunc = SuspensionHostExtensions.EnsureLoadAppStateFunc; - _previousSuspensionDriver = SuspensionHostExtensions.SuspensionDriver; - } - - /// - /// Restores the static fields in SuspensionHostExtensions after each test. - /// - [After(Test)] - public void RestoreStaticState() - { - SuspensionHostExtensions.EnsureLoadAppStateFunc = _previousEnsureLoadAppStateFunc; - SuspensionHostExtensions.SuspensionDriver = _previousSuspensionDriver; - } - [Test] - public async Task SetupDefaultSuspendResume_WithNullDriver_LogsError() + public async Task DummySuspensionDriver_InvalidateState_ReturnsUnitObservable() { - using var host = new SuspensionHost - { - IsLaunchingNew = Observable.Never(), - IsResuming = Observable.Never(), - ShouldPersistState = Observable.Never(), - ShouldInvalidateState = Observable.Never() - }; + var driver = new DummySuspensionDriver(); + var wasCalled = false; - var previousDrivers = Locator.Current.GetServices().ToList(); - Locator.CurrentMutable.UnregisterAll(); - try - { - // First call sets up with null driver and logs error - var disposable = host.SetupDefaultSuspendResume(null); + using var subscription = driver.InvalidateState() + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(_ => wasCalled = true); - // Verify a disposable is returned (might be empty or composite depending on static state) - await Assert.That(disposable).IsNotNull(); - } - finally - { - foreach (var driver in previousDrivers) - { - Locator.CurrentMutable.RegisterConstant(driver); - } - } + await Assert.That(wasCalled).IsTrue(); } [Test] - public async Task SetupDefaultSuspendResume_WithProvidedDriver_UsesProvidedDriver() + public async Task DummySuspensionDriver_LoadState_ReturnsDefaultObservable() { - using var host = new SuspensionHost - { - IsLaunchingNew = Observable.Never(), - IsResuming = Observable.Never(), - ShouldPersistState = Observable.Never(), - ShouldInvalidateState = Observable.Never(), - CreateNewAppState = () => new DummyAppState() - }; - - var driver = new TestSuspensionDriver(); - driver.StateToLoad = new DummyAppState(); + var driver = new DummySuspensionDriver(); + object? result = null; - using var disposable = host.SetupDefaultSuspendResume(driver); + using var subscription = driver.LoadState() + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(state => result = state); - await Assert.That(disposable).IsNotNull(); + await Assert.That(result).IsNull(); } [Test] - public async Task SetupDefaultSuspendResume_ShouldInvalidateState_CallsDriverInvalidateState() + public async Task DummySuspensionDriver_SaveState_ReturnsUnitObservable() { - using var host = new SuspensionHost - { - IsLaunchingNew = Observable.Never(), - IsResuming = Observable.Never(), - ShouldPersistState = Observable.Never() - }; - - var driver = new TestSuspensionDriver(); - var invalidateSubject = new Subject(); - host.ShouldInvalidateState = invalidateSubject.ObserveOn(ImmediateScheduler.Instance); - - using var disposable = host.SetupDefaultSuspendResume(driver); + var driver = new DummySuspensionDriver(); + var wasCalled = false; - invalidateSubject.OnNext(Unit.Default); + using var subscription = driver.SaveState(new DummyAppState()) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(_ => wasCalled = true); - await Assert.That(driver.InvalidateStateCallCount).IsEqualTo(1); + await Assert.That(wasCalled).IsTrue(); } + /// + /// Verifies that EnsureLoadAppState with null driver after initially having one logs error and AppState remains null. + /// + /// A representing the asynchronous operation. [Test] - public async Task SetupDefaultSuspendResume_ShouldPersistState_CallsDriverSaveState() + public async Task EnsureLoadAppState_DriverBecomesNull_LogsErrorAndAppStateRemainsNull() { - var appState = new DummyAppState(); using var host = new SuspensionHost { - AppState = appState, + CreateNewAppState = () => new DummyAppState(), IsLaunchingNew = Observable.Never(), IsResuming = Observable.Never(), + ShouldPersistState = Observable.Never(), ShouldInvalidateState = Observable.Never() }; var driver = new TestSuspensionDriver(); - var persistSubject = new Subject(); - host.ShouldPersistState = persistSubject.ObserveOn(ImmediateScheduler.Instance); + driver.StateToLoad = new DummyAppState(); + // Set up with a driver using var disposable = host.SetupDefaultSuspendResume(driver); - var token = Disposable.Empty; - persistSubject.OnNext(token); - - await Assert.That(driver.SaveStateCallCount).IsEqualTo(1); - await Assert.That(driver.LastSavedState).IsSameReferenceAs(appState); - } - - [Test] - public async Task SetupDefaultSuspendResume_IsResumingOrIsLaunchingNew_TriggersStateLoad() - { - using var host = new SuspensionHost + // Clear both the static driver AND service locator to force the null branch in EnsureLoadAppState + var previousDrivers = Splat.Locator.Current.GetServices().ToList(); + Splat.Locator.CurrentMutable.UnregisterAll(); + try { - CreateNewAppState = () => new DummyAppState(), - ShouldPersistState = Observable.Never(), - ShouldInvalidateState = Observable.Never() - }; - - var driver = new TestSuspensionDriver(); - var launchSubject = new Subject(); - var resumeSubject = new Subject(); - - host.IsLaunchingNew = launchSubject.ObserveOn(ImmediateScheduler.Instance); - host.IsResuming = resumeSubject.ObserveOn(ImmediateScheduler.Instance); - - using var disposable = host.SetupDefaultSuspendResume(driver); + SuspensionHostExtensions.SuspensionDriver = null; - launchSubject.OnNext(Unit.Default); + // Now call GetAppState which should trigger EnsureLoadAppState + // It should hit the null driver branch and log error, leaving AppState null + var state = host.GetAppState(); - await Assert.That(host.AppState).IsNotNull(); + // State should be null since driver became null and couldn't load + await Assert.That(state).IsNull(); + } + finally + { + foreach (var previousDriver in previousDrivers) + { + Splat.Locator.CurrentMutable.RegisterConstant(previousDriver); + } + } } [Test] - public async Task GetAppState_TriggersLoadOnFirstCall() + public async Task EnsureLoadAppState_LoadStateThrows_CreatesNewAppState() { using var host = new SuspensionHost { @@ -167,7 +113,7 @@ public async Task GetAppState_TriggersLoadOnFirstCall() }; var driver = new TestSuspensionDriver(); - driver.StateToLoad = new DummyAppState(); + driver.ShouldThrowOnLoad = true; using var disposable = host.SetupDefaultSuspendResume(driver); @@ -178,11 +124,12 @@ public async Task GetAppState_TriggersLoadOnFirstCall() } [Test] - public async Task GetAppState_OnlyLoadsOnce() + public async Task EnsureLoadAppState_WithExistingAppState_DoesNotLoad() { + var existingState = new DummyAppState(); using var host = new SuspensionHost { - CreateNewAppState = () => new DummyAppState(), + AppState = existingState, IsLaunchingNew = Observable.Never(), IsResuming = Observable.Never(), ShouldPersistState = Observable.Never(), @@ -190,24 +137,21 @@ public async Task GetAppState_OnlyLoadsOnce() }; var driver = new TestSuspensionDriver(); - driver.StateToLoad = new DummyAppState(); using var disposable = host.SetupDefaultSuspendResume(driver); - var state1 = host.GetAppState(); - var state2 = host.GetAppState(); + var state = host.GetAppState(); - await Assert.That(driver.LoadStateCallCount).IsEqualTo(1); - await Assert.That(state1).IsSameReferenceAs(state2); + await Assert.That(driver.LoadStateCallCount).IsEqualTo(0); + await Assert.That(state).IsSameReferenceAs(existingState); } [Test] - public async Task EnsureLoadAppState_WithExistingAppState_DoesNotLoad() + public async Task EnsureLoadAppState_WithNullCreateNewAppState_SetsAppStateToNull() { - var existingState = new DummyAppState(); using var host = new SuspensionHost { - AppState = existingState, + CreateNewAppState = null, IsLaunchingNew = Observable.Never(), IsResuming = Observable.Never(), ShouldPersistState = Observable.Never(), @@ -215,17 +159,17 @@ public async Task EnsureLoadAppState_WithExistingAppState_DoesNotLoad() }; var driver = new TestSuspensionDriver(); + driver.ShouldThrowOnLoad = true; using var disposable = host.SetupDefaultSuspendResume(driver); var state = host.GetAppState(); - await Assert.That(driver.LoadStateCallCount).IsEqualTo(0); - await Assert.That(state).IsSameReferenceAs(existingState); + await Assert.That(state).IsNull(); } [Test] - public async Task EnsureLoadAppState_LoadStateThrows_CreatesNewAppState() + public async Task GetAppState_OnlyLoadsOnce() { using var host = new SuspensionHost { @@ -237,22 +181,23 @@ public async Task EnsureLoadAppState_LoadStateThrows_CreatesNewAppState() }; var driver = new TestSuspensionDriver(); - driver.ShouldThrowOnLoad = true; + driver.StateToLoad = new DummyAppState(); using var disposable = host.SetupDefaultSuspendResume(driver); - var state = host.GetAppState(); + var state1 = host.GetAppState(); + var state2 = host.GetAppState(); - await Assert.That(state).IsNotNull(); await Assert.That(driver.LoadStateCallCount).IsEqualTo(1); + await Assert.That(state1).IsSameReferenceAs(state2); } [Test] - public async Task EnsureLoadAppState_WithNullCreateNewAppState_SetsAppStateToNull() + public async Task GetAppState_TriggersLoadOnFirstCall() { using var host = new SuspensionHost { - CreateNewAppState = null, + CreateNewAppState = () => new DummyAppState(), IsLaunchingNew = Observable.Never(), IsResuming = Observable.Never(), ShouldPersistState = Observable.Never(), @@ -260,65 +205,24 @@ public async Task EnsureLoadAppState_WithNullCreateNewAppState_SetsAppStateToNul }; var driver = new TestSuspensionDriver(); - driver.ShouldThrowOnLoad = true; + driver.StateToLoad = new DummyAppState(); using var disposable = host.SetupDefaultSuspendResume(driver); var state = host.GetAppState(); - await Assert.That(state).IsNull(); - } - - [Test] - public async Task DummySuspensionDriver_LoadState_ReturnsDefaultObservable() - { - var driver = new DummySuspensionDriver(); - object? result = null; - - using var subscription = driver.LoadState() - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(state => result = state); - - await Assert.That(result).IsNull(); - } - - [Test] - public async Task DummySuspensionDriver_SaveState_ReturnsUnitObservable() - { - var driver = new DummySuspensionDriver(); - var wasCalled = false; - - using var subscription = driver.SaveState(new DummyAppState()) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(_ => wasCalled = true); - - await Assert.That(wasCalled).IsTrue(); - } - - [Test] - public async Task DummySuspensionDriver_InvalidateState_ReturnsUnitObservable() - { - var driver = new DummySuspensionDriver(); - var wasCalled = false; - - using var subscription = driver.InvalidateState() - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(_ => wasCalled = true); - - await Assert.That(wasCalled).IsTrue(); + await Assert.That(state).IsNotNull(); + await Assert.That(driver.LoadStateCallCount).IsEqualTo(1); } /// - /// Verifies that GetAppState correctly retrieves the current app state. + /// Verifies that GetAppState correctly retrieves the current app state. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task GetAppStateReturns() { - var fixture = new SuspensionHost - { - AppState = new DummyAppState() - }; + var fixture = new SuspensionHost { AppState = new DummyAppState() }; var result = fixture.GetAppState(); @@ -326,19 +230,17 @@ public async Task GetAppStateReturns() } /// - /// Verifies that a null throws when calling SetupDefaultSuspendResume. + /// Verifies that GetAppState throws for null ISuspensionHost. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task NullSuspensionHostThrowsException() - { - await Assert.That(static () => ((SuspensionHost)null!).SetupDefaultSuspendResume()).Throws(); - } + public async Task GetAppStateThrowsForNullHost() => await Assert + .That(() => ((ISuspensionHost)null!).GetAppState()).Throws(); /// - /// Verifies that a null AppState does not throw when calling SetupDefaultSuspendResume. + /// Verifies that a null AppState does not throw when calling SetupDefaultSuspendResume. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task NullAppStateDoesNotThrowException() { @@ -348,9 +250,18 @@ public async Task NullAppStateDoesNotThrowException() } /// - /// Verifies that observing AppState does not throw. + /// Verifies that a null throws when calling + /// SetupDefaultSuspendResume. + /// + /// A representing the asynchronous operation. + [Test] + public async Task NullSuspensionHostThrowsException() => await Assert + .That(static () => ((SuspensionHost)null!).SetupDefaultSuspendResume()).Throws(); + + /// + /// Verifies that observing AppState does not throw. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ObserveAppStateDoesNotThrowException() { @@ -360,9 +271,9 @@ public async Task ObserveAppStateDoesNotThrowException() } /// - /// Verifies that observing AppState does not throw . + /// Verifies that observing AppState does not throw . /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ObserveAppStateDoesNotThrowInvalidCastException() { @@ -372,9 +283,9 @@ public async Task ObserveAppStateDoesNotThrowInvalidCastException() } /// - /// Verifies that ObserveAppState emits values when AppState changes. + /// Verifies that ObserveAppState emits values when AppState changes. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ObserveAppStateEmitsValues() { @@ -397,9 +308,9 @@ public async Task ObserveAppStateEmitsValues() } /// - /// Verifies that ObserveAppState filters null values. + /// Verifies that ObserveAppState filters null values. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task ObserveAppStateFiltersNullValues() { @@ -420,110 +331,229 @@ public async Task ObserveAppStateFiltersNullValues() } /// - /// Verifies that GetAppState throws for null ISuspensionHost. + /// Verifies that ObserveAppState throws for null ISuspensionHost. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task GetAppStateThrowsForNullHost() - { - await Assert.That(() => ((ISuspensionHost)null!).GetAppState()).Throws(); - } + public async Task ObserveAppStateThrowsForNullHost() => await Assert + .That(() => ((ISuspensionHost)null!).ObserveAppState()).Throws(); /// - /// Verifies that ObserveAppState throws for null ISuspensionHost. + /// Restores the static fields in SuspensionHostExtensions after each test. /// - /// A representing the asynchronous operation. - [Test] - public async Task ObserveAppStateThrowsForNullHost() + [After(Test)] + public void RestoreStaticState() { - await Assert.That(() => ((ISuspensionHost)null!).ObserveAppState()).Throws(); + SuspensionHostExtensions.EnsureLoadAppStateFunc = _previousEnsureLoadAppStateFunc; + SuspensionHostExtensions.SuspensionDriver = _previousSuspensionDriver; } /// - /// Verifies that EnsureLoadAppState with null driver after initially having one logs error and AppState remains null. + /// Saves the static state before each test. /// - /// A representing the asynchronous operation. + [Before(Test)] + public void SaveStaticState() + { + _previousEnsureLoadAppStateFunc = SuspensionHostExtensions.EnsureLoadAppStateFunc; + _previousSuspensionDriver = SuspensionHostExtensions.SuspensionDriver; + } + [Test] - public async Task EnsureLoadAppState_DriverBecomesNull_LogsErrorAndAppStateRemainsNull() + public async Task SetupDefaultSuspendResume_IsResumingOrIsLaunchingNew_TriggersStateLoad() { using var host = new SuspensionHost { CreateNewAppState = () => new DummyAppState(), + ShouldPersistState = Observable.Never(), + ShouldInvalidateState = Observable.Never() + }; + + var driver = new TestSuspensionDriver(); + var launchSubject = new Subject(); + var resumeSubject = new Subject(); + + host.IsLaunchingNew = launchSubject.ObserveOn(ImmediateScheduler.Instance); + host.IsResuming = resumeSubject.ObserveOn(ImmediateScheduler.Instance); + + using var disposable = host.SetupDefaultSuspendResume(driver); + + launchSubject.OnNext(Unit.Default); + + await Assert.That(host.AppState).IsNotNull(); + } + + [Test] + public async Task SetupDefaultSuspendResume_ShouldInvalidateState_CallsDriverInvalidateState() + { + using var host = new SuspensionHost + { + IsLaunchingNew = Observable.Never(), + IsResuming = Observable.Never(), + ShouldPersistState = Observable.Never() + }; + + var driver = new TestSuspensionDriver(); + var invalidateSubject = new Subject(); + host.ShouldInvalidateState = invalidateSubject.ObserveOn(ImmediateScheduler.Instance); + + using var disposable = host.SetupDefaultSuspendResume(driver); + + invalidateSubject.OnNext(Unit.Default); + + await Assert.That(driver.InvalidateStateCallCount).IsEqualTo(1); + } + + [Test] + public async Task SetupDefaultSuspendResume_ShouldPersistState_CallsDriverSaveState() + { + var appState = new DummyAppState(); + using var host = new SuspensionHost + { + AppState = appState, IsLaunchingNew = Observable.Never(), IsResuming = Observable.Never(), - ShouldPersistState = Observable.Never(), ShouldInvalidateState = Observable.Never() }; var driver = new TestSuspensionDriver(); - driver.StateToLoad = new DummyAppState(); + var persistSubject = new Subject(); + host.ShouldPersistState = persistSubject.ObserveOn(ImmediateScheduler.Instance); - // Set up with a driver using var disposable = host.SetupDefaultSuspendResume(driver); - // Clear both the static driver AND service locator to force the null branch in EnsureLoadAppState - var previousDrivers = Locator.Current.GetServices().ToList(); - Locator.CurrentMutable.UnregisterAll(); - try + var token = Disposable.Empty; + persistSubject.OnNext(token); + + await Assert.That(driver.SaveStateCallCount).IsEqualTo(1); + await Assert.That(driver.LastSavedState).IsSameReferenceAs(appState); + } + + [Test] + public async Task SetupDefaultSuspendResume_WithNullDriver_LogsError() + { + using var host = new SuspensionHost { - SuspensionHostExtensions.SuspensionDriver = null; + IsLaunchingNew = Observable.Never(), + IsResuming = Observable.Never(), + ShouldPersistState = Observable.Never(), + ShouldInvalidateState = Observable.Never() + }; - // Now call GetAppState which should trigger EnsureLoadAppState - // It should hit the null driver branch and log error, leaving AppState null - var state = host.GetAppState(); + var previousDrivers = Splat.Locator.Current.GetServices().ToList(); + Splat.Locator.CurrentMutable.UnregisterAll(); + try + { + // First call sets up with null driver and logs error + var disposable = host.SetupDefaultSuspendResume(); - // State should be null since driver became null and couldn't load - await Assert.That(state).IsNull(); + // Verify a disposable is returned (might be empty or composite depending on static state) + await Assert.That(disposable).IsNotNull(); } finally { - foreach (var previousDriver in previousDrivers) + foreach (var driver in previousDrivers) { - Locator.CurrentMutable.RegisterConstant(previousDriver); + Splat.Locator.CurrentMutable.RegisterConstant(driver); } } } + [Test] + public async Task SetupDefaultSuspendResume_WithProvidedDriver_UsesProvidedDriver() + { + using var host = new SuspensionHost + { + IsLaunchingNew = Observable.Never(), + IsResuming = Observable.Never(), + ShouldPersistState = Observable.Never(), + ShouldInvalidateState = Observable.Never(), + CreateNewAppState = () => new DummyAppState() + }; + + var driver = new TestSuspensionDriver(); + driver.StateToLoad = new DummyAppState(); + + using var disposable = host.SetupDefaultSuspendResume(driver); + + await Assert.That(disposable).IsNotNull(); + } + + private class DummyAppState + { + } + private class TestSuspensionDriver : ISuspensionDriver { + public int InvalidateStateCallCount { get; private set; } + + public object? LastSavedState { get; private set; } + public int LoadStateCallCount { get; private set; } public int SaveStateCallCount { get; private set; } - public int InvalidateStateCallCount { get; private set; } + public bool ShouldThrowOnLoad { get; set; } public object? StateToLoad { get; set; } - public object? LastSavedState { get; private set; } - - public bool ShouldThrowOnLoad { get; set; } + public IObservable InvalidateState() + { + InvalidateStateCallCount++; + return Observable.Return(Unit.Default, ImmediateScheduler.Instance); + } - public IObservable LoadState() + [RequiresUnreferencedCode( + "Implementations commonly use reflection-based serialization. Prefer LoadState(JsonTypeInfo) for trimming or AOT scenarios.")] + [RequiresDynamicCode( + "Implementations commonly use reflection-based serialization. Prefer LoadState(JsonTypeInfo) for trimming or AOT scenarios.")] + public IObservable LoadState() { LoadStateCallCount++; if (ShouldThrowOnLoad) { - return Observable.Throw(new InvalidOperationException("Failed to load state"), ImmediateScheduler.Instance); + return Observable.Throw( + new InvalidOperationException("Failed to load state"), + ImmediateScheduler.Instance); } return Observable.Return(StateToLoad ?? new DummyAppState(), ImmediateScheduler.Instance); } - public IObservable SaveState(object state) + public IObservable LoadState(JsonTypeInfo typeInfo) + { + LoadStateCallCount++; + if (ShouldThrowOnLoad) + { + return Observable.Throw( + new InvalidOperationException("Failed to load state"), + ImmediateScheduler.Instance); + } + + // For test purposes, try to cast StateToLoad to T + if (StateToLoad is T typedState) + { + return Observable.Return(typedState, ImmediateScheduler.Instance); + } + + return Observable.Return(default, ImmediateScheduler.Instance); + } + + [RequiresUnreferencedCode( + "Implementations commonly use reflection-based serialization. Prefer SaveState(T, JsonTypeInfo) for trimming or AOT scenarios.")] + [RequiresDynamicCode( + "Implementations commonly use reflection-based serialization. Prefer SaveState(T, JsonTypeInfo) for trimming or AOT scenarios.")] + public IObservable SaveState(T state) { SaveStateCallCount++; LastSavedState = state; return Observable.Return(Unit.Default, ImmediateScheduler.Instance); } - public IObservable InvalidateState() + public IObservable SaveState(T state, JsonTypeInfo typeInfo) { - InvalidateStateCallCount++; + SaveStateCallCount++; + LastSavedState = state; return Observable.Return(Unit.Default, ImmediateScheduler.Instance); } } - - private class DummyAppState - { - } } diff --git a/src/tests/ReactiveUI.Tests/UnhandledInteractionExceptionTest.cs b/src/tests/ReactiveUI.Tests/UnhandledInteractionExceptionTest.cs index af39ae81b8..2d792d6df3 100644 --- a/src/tests/ReactiveUI.Tests/UnhandledInteractionExceptionTest.cs +++ b/src/tests/ReactiveUI.Tests/UnhandledInteractionExceptionTest.cs @@ -6,14 +6,14 @@ namespace ReactiveUI.Tests; /// -/// Tests for . +/// Tests for . /// public class UnhandledInteractionExceptionTest { /// - /// Tests that parameterless constructor creates exception. + /// Tests that parameterless constructor creates exception. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task Constructor_Parameterless_CreatesException() { @@ -25,56 +25,56 @@ public async Task Constructor_Parameterless_CreatesException() } /// - /// Tests that constructor with message creates exception. + /// Tests that constructor with interaction and input sets properties. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task Constructor_WithMessage_SetsMessage() + public async Task Constructor_WithInteractionAndInput_SetsProperties() { - var message = "Test error message"; + var interaction = new Interaction(); + var input = "test input"; - var exception = new UnhandledInteractionException(message); + var exception = new UnhandledInteractionException(interaction, input); - await Assert.That(exception.Message).IsEqualTo(message); + await Assert.That(exception.Interaction).IsEqualTo(interaction); + await Assert.That(exception.Input).IsEqualTo(input); + await Assert.That(exception.Message).Contains("Failed to find a registration"); } /// - /// Tests that constructor with message and inner exception creates exception. + /// Tests that constructor with message creates exception. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task Constructor_WithMessageAndInnerException_SetsProperties() + public async Task Constructor_WithMessage_SetsMessage() { var message = "Test error message"; - var innerException = new InvalidOperationException("Inner"); - var exception = new UnhandledInteractionException(message, innerException); + var exception = new UnhandledInteractionException(message); await Assert.That(exception.Message).IsEqualTo(message); - await Assert.That(exception.InnerException).IsEqualTo(innerException); } /// - /// Tests that constructor with interaction and input sets properties. + /// Tests that constructor with message and inner exception creates exception. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task Constructor_WithInteractionAndInput_SetsProperties() + public async Task Constructor_WithMessageAndInnerException_SetsProperties() { - var interaction = new Interaction(); - var input = "test input"; + var message = "Test error message"; + var innerException = new InvalidOperationException("Inner"); - var exception = new UnhandledInteractionException(interaction, input); + var exception = new UnhandledInteractionException(message, innerException); - await Assert.That(exception.Interaction).IsEqualTo(interaction); - await Assert.That(exception.Input).IsEqualTo(input); - await Assert.That(exception.Message).Contains("Failed to find a registration"); + await Assert.That(exception.Message).IsEqualTo(message); + await Assert.That(exception.InnerException).IsEqualTo(innerException); } /// - /// Tests that exception can be thrown and caught. + /// Tests that exception can be thrown and caught. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task Exception_CanBeThrownAndCaught() { @@ -86,9 +86,9 @@ public async Task Exception_CanBeThrownAndCaught() } /// - /// Tests that Input property returns the input value. + /// Tests that Input property returns the input value. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task Input_ReturnsInputValue() { @@ -101,9 +101,9 @@ public async Task Input_ReturnsInputValue() } /// - /// Tests that Interaction property returns the interaction. + /// Tests that Interaction property returns the interaction. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task Interaction_ReturnsInteraction() { diff --git a/src/tests/ReactiveUI.Tests/Utilities/CompatMixins.cs b/src/tests/ReactiveUI.Tests/Utilities/CompatMixins.cs index 3572e1daab..986e4740cb 100644 --- a/src/tests/ReactiveUI.Tests/Utilities/CompatMixins.cs +++ b/src/tests/ReactiveUI.Tests/Utilities/CompatMixins.cs @@ -3,7 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Utilities; public static class CompatMixins { diff --git a/src/tests/ReactiveUI.Tests/Utilities/CompatMixinsTests.cs b/src/tests/ReactiveUI.Tests/Utilities/CompatMixinsTests.cs new file mode 100644 index 0000000000..fe16e80ab2 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Utilities/CompatMixinsTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) 2025 .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.Utilities; + +/// +/// Tests for CompatMixins utility methods. +/// +public class CompatMixinsTests +{ + /// + /// Tests that Run extension method processes all items. + /// + /// A representing the asynchronous operation. + [Test] + public async Task Run_ProcessesAllItems() + { + // Arrange + var items = new[] { 1, 2, 3, 4, 5 }; + var processedItems = new List(); + + // Act + items.Run(x => processedItems.Add(x * 2)); + + // Assert + await Assert.That(processedItems).IsEquivalentTo([2, 4, 6, 8, 10]); + } + + /// + /// Tests that SkipLast with count greater than collection returns empty. + /// + /// A representing the asynchronous operation. + [Test] + public async Task SkipLast_CountGreaterThanCollection_ReturnsEmpty() + { + // Arrange + var items = new[] { 1, 2, 3 }; + + // Act + var result = items.SkipLast(10).ToList(); + + // Assert + await Assert.That(result).IsEmpty(); + } + + /// + /// Tests that SkipLast extension method removes last N items. + /// + /// A representing the asynchronous operation. + [Test] + public async Task SkipLast_RemovesLastNItems() + { + // Arrange + var items = new[] { 1, 2, 3, 4, 5 }; + + // Act + var result = items.SkipLast(2).ToList(); + + // Assert + await Assert.That(result).IsEquivalentTo([1, 2, 3]); + } + + /// + /// Tests that SkipLast with zero count returns all items. + /// + /// A representing the asynchronous operation. + [Test] + public async Task SkipLast_ZeroCount_ReturnsAllItems() + { + // Arrange + var items = new[] { 1, 2, 3, 4, 5 }; + + // Act + var result = items.SkipLast(0).ToList(); + + // Assert + await Assert.That(result).IsEquivalentTo([1, 2, 3, 4, 5]); + } +} diff --git a/src/tests/ReactiveUI.Tests/Utilities/CountingTestScheduler.cs b/src/tests/ReactiveUI.Tests/Utilities/CountingTestScheduler.cs index 1f0a0e35a6..745f1a6b96 100644 --- a/src/tests/ReactiveUI.Tests/Utilities/CountingTestScheduler.cs +++ b/src/tests/ReactiveUI.Tests/Utilities/CountingTestScheduler.cs @@ -3,32 +3,35 @@ // 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; +namespace ReactiveUI.Tests.Utilities; public class CountingTestScheduler(IScheduler innerScheduler) : IScheduler { public IScheduler InnerScheduler { get; } = innerScheduler; - public List<(Action action, TimeSpan? dueTime)> ScheduledItems { get; } = []; - - /// + /// public DateTimeOffset Now => InnerScheduler.Now; - /// - public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) + public List<(Action action, TimeSpan? dueTime)> ScheduledItems { get; } = []; + + /// + public IDisposable Schedule( + TState state, + DateTimeOffset dueTime, + Func action) { ScheduledItems.Add((() => action(this, state), null)); return InnerScheduler.Schedule(state, dueTime, action); } - /// + /// public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { ScheduledItems.Add((() => action(this, state), dueTime)); return InnerScheduler.Schedule(state, dueTime, action); } - /// + /// public IDisposable Schedule(TState state, Func action) { ScheduledItems.Add((() => action(this, state), null)); diff --git a/src/tests/ReactiveUI.Tests/Utilities/DisposableMixinsTests.cs b/src/tests/ReactiveUI.Tests/Utilities/DisposableMixinsTests.cs new file mode 100644 index 0000000000..9a4027915b --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Utilities/DisposableMixinsTests.cs @@ -0,0 +1,72 @@ +// Copyright (c) 2025 .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.Reactive.Disposables.Fluent; + +namespace ReactiveUI.Tests.Utilities; + +/// +/// Tests for DisposableMixins utility methods. +/// +public class DisposableMixinsTests +{ + /// + /// Tests that DisposeWith adds disposable to composite. + /// + /// A representing the asynchronous operation. + [Test] + public async Task DisposeWith_AddsToComposite() + { + // Arrange + var disposable1 = Disposable.Create(() => { }); + var disposable2 = Disposable.Create(() => { }); + var compositeDisposable = new CompositeDisposable(); + + // Act + disposable1.DisposeWith(compositeDisposable); + disposable2.DisposeWith(compositeDisposable); + + // Assert + await Assert.That(compositeDisposable).Count().IsEqualTo(2); + } + + /// + /// Tests that DisposeWith disposes when composite is disposed. + /// + /// A representing the asynchronous operation. + [Test] + public async Task DisposeWith_DisposesWhenCompositeDisposed() + { + // Arrange + var disposed = false; + var disposable = Disposable.Create(() => disposed = true); + var compositeDisposable = new CompositeDisposable(); + + // Act + disposable.DisposeWith(compositeDisposable); + compositeDisposable.Dispose(); + + // Assert + await Assert.That(disposed).IsTrue(); + } + + /// + /// Tests that DisposeWith returns original disposable. + /// + /// A representing the asynchronous operation. + [Test] + public async Task DisposeWith_ReturnsOriginalDisposable() + { + // Arrange + var disposable = Disposable.Create(() => { }); + var compositeDisposable = new CompositeDisposable(); + + // Act + var result = disposable.DisposeWith(compositeDisposable); + + // Assert + await Assert.That(result).IsSameReferenceAs(disposable); + } +} diff --git a/src/tests/ReactiveUI.Tests/Utilities/EnumerableTestMixin.cs b/src/tests/ReactiveUI.Tests/Utilities/EnumerableTestMixin.cs index b07a14f2bd..71fd34627e 100644 --- a/src/tests/ReactiveUI.Tests/Utilities/EnumerableTestMixin.cs +++ b/src/tests/ReactiveUI.Tests/Utilities/EnumerableTestMixin.cs @@ -3,9 +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.Diagnostics; - -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Utilities; public static class EnumerableTestMixin { diff --git a/src/tests/ReactiveUI.Tests/Utilities/JsonHelper.cs b/src/tests/ReactiveUI.Tests/Utilities/JsonHelper.cs index 74d0e290c9..6d6ae19e3d 100644 --- a/src/tests/ReactiveUI.Tests/Utilities/JsonHelper.cs +++ b/src/tests/ReactiveUI.Tests/Utilities/JsonHelper.cs @@ -4,12 +4,25 @@ // See the LICENSE file in the project root for full license information. using System.IO; +using System.Runtime.Serialization.Json; using System.Text; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.Utilities; public static class JSONHelper { + public static T? Deserialize(string json) + where T : class + { + var obj = Activator.CreateInstance(); + + var ms = new MemoryStream(Encoding.Unicode.GetBytes(json)); + var serializer = new DataContractJsonSerializer(obj.GetType()); + obj = serializer.ReadObject(ms) as T; + ms.Close(); + return obj; + } + public static string? Serialize(T serializeObject) { if (serializeObject is null) @@ -17,21 +30,9 @@ public static class JSONHelper return null; } - var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(serializeObject.GetType()); + var serializer = new DataContractJsonSerializer(serializeObject.GetType()); var ms = new MemoryStream(); serializer.WriteObject(ms, serializeObject); return Encoding.Default.GetString(ms.ToArray()); } - - public static T? Deserialize(string json) - where T : class - { - var obj = Activator.CreateInstance(); - - var ms = new MemoryStream(Encoding.Unicode.GetBytes(json)); - var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType()); - obj = serializer.ReadObject(ms) as T; - ms.Close(); - return obj; - } } diff --git a/src/tests/ReactiveUI.Tests/VariadicTemplatesTest.cs b/src/tests/ReactiveUI.Tests/VariadicTemplatesTest.cs index cfd6fb7d54..999827ad7f 100644 --- a/src/tests/ReactiveUI.Tests/VariadicTemplatesTest.cs +++ b/src/tests/ReactiveUI.Tests/VariadicTemplatesTest.cs @@ -8,1019 +8,1672 @@ namespace ReactiveUI.Tests; public class VariadicTemplatesTest { [Test] - public async Task WhenAnyValue_1Prop_Expr() + public async Task WhenAny_10Props_Sel() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue(x => x.Property1).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + x => x.Property10, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); - vm.Property1 = "a"; - await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyValue_1Prop_Str() + public async Task WhenAny_10Props_Sel_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue(nameof(TestViewModel.Property1)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + x => x.Property10, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_1Prop_Expr_Dist() + public async Task WhenAny_10Props_Sel_Str() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue(x => x.Property1, isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_1Prop_Str_Dist() + public async Task WhenAny_10Props_Sel_Str_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue(nameof(TestViewModel.Property1), isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_1Props_Sel() + public async Task WhenAny_11Props_Sel() { var vm = new TestViewModel(); var list = new List(); - Func selector = (v1) => "x"; - vm.WhenAnyValue(x => x.Property1, selector).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + x => x.Property10, + x => x.Property11, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_1Props_Sel_Str() + public async Task WhenAny_11Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - (v1) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + x => x.Property10, + x => x.Property11, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_1Props_Sel_Dist() + public async Task WhenAny_11Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - Func selector = (v1) => "x"; - vm.WhenAnyValue(x => x.Property1, selector, isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + nameof(TestViewModel.Property11), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_1Props_Sel_Str_Dist() + public async Task WhenAny_11Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - (v1) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + nameof(TestViewModel.Property11), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_1Props_Sel() + public async Task WhenAny_12Props_Sel() { var vm = new TestViewModel(); var list = new List(); vm.WhenAny( x => x.Property1, - (v1) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + x => x.Property10, + x => x.Property11, + x => x.Property12, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_1Props_Sel_Str() + public async Task WhenAny_12Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - (v1) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + x => x.Property10, + x => x.Property11, + x => x.Property12, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_1Props_Sel_Dist() + public async Task WhenAny_12Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - x => x.Property1, - (v1) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + nameof(TestViewModel.Property11), + nameof(TestViewModel.Property12), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_1Props_Sel_Str_Dist() + public async Task WhenAny_12Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - (v1) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + nameof(TestViewModel.Property11), + nameof(TestViewModel.Property12), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_2Props_Tuple_Expr() + public async Task WhenAny_1Props_Sel() { var vm = new TestViewModel(); - var list = new List<(string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + x => x.Property1, + v1 => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_2Props_Tuple_Str() + public async Task WhenAny_1Props_Sel_Dist() { var vm = new TestViewModel(); - var list = new List<(string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + x => x.Property1, + v1 => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_2Props_Tuple_Expr_Dist() + public async Task WhenAny_1Props_Sel_Str() { var vm = new TestViewModel(); - var list = new List<(string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + nameof(TestViewModel.Property1), + v1 => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_2Props_Tuple_Str_Dist() + public async Task WhenAny_1Props_Sel_Str_Dist() { var vm = new TestViewModel(); - var list = new List<(string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + nameof(TestViewModel.Property1), + v1 => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_2Props_Sel() + public async Task WhenAny_2Props_Sel() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_2Props_Sel_Str() + public async Task WhenAny_2Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + (v1, v2) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_2Props_Sel_Dist() + public async Task WhenAny_2Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - (v1, v2) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_2Props_Sel_Str_Dist() + public async Task WhenAny_2Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - (v1, v2) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + (v1, v2) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_2Props_Sel() + public async Task WhenAny_3Props_Sel() { var vm = new TestViewModel(); var list = new List(); vm.WhenAny( - x => x.Property1, - x => x.Property2, - (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + x => x.Property3, + (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_2Props_Sel_Str() + public async Task WhenAny_3Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + (v1, v2, v3) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_2Props_Sel_Dist() + public async Task WhenAny_3Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - (v1, v2) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_2Props_Sel_Str_Dist() + public async Task WhenAny_3Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - (v1, v2) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + (v1, v2, v3) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_3Props_Tuple_Expr() + public async Task WhenAny_4Props_Sel() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_3Props_Tuple_Str() + public async Task WhenAny_4Props_Sel_Dist() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + (v1, v2, v3, v4) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_3Props_Tuple_Expr_Dist() + public async Task WhenAny_4Props_Sel_Str() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_3Props_Tuple_Str_Dist() + public async Task WhenAny_4Props_Sel_Str_Dist() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + (v1, v2, v3, v4) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_3Props_Sel() + public async Task WhenAny_5Props_Sel() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_3Props_Sel_Str() + public async Task WhenAny_5Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + (v1, v2, v3, v4, v5) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_3Props_Sel_Dist() + public async Task WhenAny_5Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - (v1, v2, v3) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_3Props_Sel_Str_Dist() + public async Task WhenAny_5Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - (v1, v2, v3) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + (v1, v2, v3, v4, v5) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_3Props_Sel() + public async Task WhenAny_6Props_Sel() { var vm = new TestViewModel(); var list = new List(); vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - await Assert.That(list).Count().IsGreaterThan(0); - } - - [Test] - public async Task WhenAny_3Props_Sel_Str() - { - var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_3Props_Sel_Dist() + public async Task WhenAny_6Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - (v1, v2, v3) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + (v1, v2, v3, v4, v5, v6) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_3Props_Sel_Str_Dist() + public async Task WhenAny_6Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - (v1, v2, v3) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - await Assert.That(list).Count().IsGreaterThan(0); - } - - [Test] - public async Task WhenAnyValue_4Props_Tuple_Expr() - { - var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_4Props_Tuple_Str() + public async Task WhenAny_6Props_Sel_Str_Dist() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + (v1, v2, v3, v4, v5, v6) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_4Props_Tuple_Expr_Dist() + public async Task WhenAny_7Props_Sel() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_4Props_Tuple_Str_Dist() + public async Task WhenAny_7Props_Sel_Dist() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + (v1, v2, v3, v4, v5, v6, v7) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_4Props_Sel() + public async Task WhenAny_7Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_4Props_Sel_Str() + public async Task WhenAny_7Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + (v1, v2, v3, v4, v5, v6, v7) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_4Props_Sel_Dist() + public async Task WhenAny_8Props_Sel() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - (v1, v2, v3, v4) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_4Props_Sel_Str_Dist() + public async Task WhenAny_8Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - (v1, v2, v3, v4) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + (v1, v2, v3, v4, v5, v6, v7, v8) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_4Props_Sel() + public async Task WhenAny_8Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_4Props_Sel_Str() + public async Task WhenAny_8Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + (v1, v2, v3, v4, v5, v6, v7, v8) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_4Props_Sel_Dist() + public async Task WhenAny_9Props_Sel() { var vm = new TestViewModel(); var list = new List(); vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - (v1, v2, v3, v4) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_4Props_Sel_Str_Dist() + public async Task WhenAny_9Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - (v1, v2, v3, v4) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAny( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_5Props_Tuple_Expr() + public async Task WhenAny_9Props_Sel_Str() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_5Props_Tuple_Str() + public async Task WhenAny_9Props_Sel_Str_Dist() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAny( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_5Props_Tuple_Expr_Dist() + public async Task WhenAnyObservable_10Props() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; + var subj9 = new Subject(); + vm.ObservableProperty9 = subj9; + var subj10 = new Subject(); + vm.ObservableProperty10 = subj10; + var list = new List(); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8, + x => x.ObservableProperty9, + x => x.ObservableProperty10).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); + subj9.OnNext("test"); + subj10.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_5Props_Tuple_Str_Dist() + public async Task WhenAnyObservable_10Props_Sel() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; + var subj9 = new Subject(); + vm.ObservableProperty9 = subj9; + var subj10 = new Subject(); + vm.ObservableProperty10 = subj10; + var list = new List(); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8, + x => x.ObservableProperty9, + x => x.ObservableProperty10, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); + subj9.OnNext("test"); + subj10.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_5Props_Sel() + public async Task WhenAnyObservable_11Props() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; + var subj9 = new Subject(); + vm.ObservableProperty9 = subj9; + var subj10 = new Subject(); + vm.ObservableProperty10 = subj10; + var subj11 = new Subject(); + vm.ObservableProperty11 = subj11; + var list = new List(); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8, + x => x.ObservableProperty9, + x => x.ObservableProperty10, + x => x.ObservableProperty11).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); + subj9.OnNext("test"); + subj10.OnNext("test"); + subj11.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_5Props_Sel_Str() + public async Task WhenAnyObservable_11Props_Sel() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; + var subj9 = new Subject(); + vm.ObservableProperty9 = subj9; + var subj10 = new Subject(); + vm.ObservableProperty10 = subj10; + var subj11 = new Subject(); + vm.ObservableProperty11 = subj11; var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8, + x => x.ObservableProperty9, + x => x.ObservableProperty10, + x => x.ObservableProperty11, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); + subj9.OnNext("test"); + subj10.OnNext("test"); + subj11.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_5Props_Sel_Dist() + public async Task WhenAnyObservable_12Props() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; + var subj9 = new Subject(); + vm.ObservableProperty9 = subj9; + var subj10 = new Subject(); + vm.ObservableProperty10 = subj10; + var subj11 = new Subject(); + vm.ObservableProperty11 = subj11; + var subj12 = new Subject(); + vm.ObservableProperty12 = subj12; var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - (v1, v2, v3, v4, v5) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8, + x => x.ObservableProperty9, + x => x.ObservableProperty10, + x => x.ObservableProperty11, + x => x.ObservableProperty12).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); + subj9.OnNext("test"); + subj10.OnNext("test"); + subj11.OnNext("test"); + subj12.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_5Props_Sel_Str_Dist() + public async Task WhenAnyObservable_12Props_Sel() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; + var subj9 = new Subject(); + vm.ObservableProperty9 = subj9; + var subj10 = new Subject(); + vm.ObservableProperty10 = subj10; + var subj11 = new Subject(); + vm.ObservableProperty11 = subj11; + var subj12 = new Subject(); + vm.ObservableProperty12 = subj12; var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - (v1, v2, v3, v4, v5) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8, + x => x.ObservableProperty9, + x => x.ObservableProperty10, + x => x.ObservableProperty11, + x => x.ObservableProperty12, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); + subj9.OnNext("test"); + subj10.OnNext("test"); + subj11.OnNext("test"); + subj12.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_5Props_Sel() + public async Task WhenAnyObservable_1Prop() { var vm = new TestViewModel(); + var subj = new Subject(); + vm.ObservableProperty1 = subj; var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable(x => x.ObservableProperty1).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_5Props_Sel_Str() + public async Task WhenAnyObservable_2Props() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_5Props_Sel_Dist() + public async Task WhenAnyObservable_2Props_Sel() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - (v1, v2, v3, v4, v5) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_5Props_Sel_Str_Dist() + public async Task WhenAnyObservable_3Props() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - (v1, v2, v3, v4, v5) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_6Props_Tuple_Expr() + public async Task WhenAnyObservable_3Props_Sel() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - await Assert.That(list).Count().IsGreaterThan(0); - } - - [Test] - public async Task WhenAnyValue_6Props_Tuple_Str() - { - var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var list = new List(); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_6Props_Tuple_Expr_Dist() + public async Task WhenAnyObservable_4Props() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var list = new List(); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_6Props_Tuple_Str_Dist() + public async Task WhenAnyObservable_4Props_Sel() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var list = new List(); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_6Props_Sel() + public async Task WhenAnyObservable_5Props() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_6Props_Sel_Str() + public async Task WhenAnyObservable_5Props_Sel() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_6Props_Sel_Dist() + public async Task WhenAnyObservable_6Props() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - (v1, v2, v3, v4, v5, v6) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_6Props_Sel_Str_Dist() + public async Task WhenAnyObservable_6Props_Sel() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - (v1, v2, v3, v4, v5, v6) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_6Props_Sel() + public async Task WhenAnyObservable_7Props() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_6Props_Sel_Str() + public async Task WhenAnyObservable_7Props_Sel() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_6Props_Sel_Dist() + public async Task WhenAnyObservable_8Props() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - (v1, v2, v3, v4, v5, v6) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - await Assert.That(list).Count().IsGreaterThan(0); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; + var list = new List(); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); + await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_6Props_Sel_Str_Dist() + public async Task WhenAnyObservable_8Props_Sel() { var vm = new TestViewModel(); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - (v1, v2, v3, v4, v5, v6) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8, + (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_7Props_Tuple_Expr() + public async Task WhenAnyObservable_9Props() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; + var subj9 = new Subject(); + vm.ObservableProperty9 = subj9; + var list = new List(); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8, + x => x.ObservableProperty9).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); + subj9.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_7Props_Tuple_Str() + public async Task WhenAnyObservable_9Props_Sel() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var subj1 = new Subject(); + vm.ObservableProperty1 = subj1; + var subj2 = new Subject(); + vm.ObservableProperty2 = subj2; + var subj3 = new Subject(); + vm.ObservableProperty3 = subj3; + var subj4 = new Subject(); + vm.ObservableProperty4 = subj4; + var subj5 = new Subject(); + vm.ObservableProperty5 = subj5; + var subj6 = new Subject(); + vm.ObservableProperty6 = subj6; + var subj7 = new Subject(); + vm.ObservableProperty7 = subj7; + var subj8 = new Subject(); + vm.ObservableProperty8 = subj8; + var subj9 = new Subject(); + vm.ObservableProperty9 = subj9; + var list = new List(); + vm.WhenAnyObservable( + x => x.ObservableProperty1, + x => x.ObservableProperty2, + x => x.ObservableProperty3, + x => x.ObservableProperty4, + x => x.ObservableProperty5, + x => x.ObservableProperty6, + x => x.ObservableProperty7, + x => x.ObservableProperty8, + x => x.ObservableProperty9, + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + subj1.OnNext("test"); + subj2.OnNext("test"); + subj3.OnNext("test"); + subj4.OnNext("test"); + subj5.OnNext("test"); + subj6.OnNext("test"); + subj7.OnNext("test"); + subj8.OnNext("test"); + subj9.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_7Props_Tuple_Expr_Dist() + public async Task WhenAnyValue_10Props_Sel() { var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); + var list = new List(); vm.WhenAnyValue( x => x.Property1, x => x.Property2, @@ -1029,50 +1682,42 @@ public async Task WhenAnyValue_7Props_Tuple_Expr_Dist() x => x.Property5, x => x.Property6, x => x.Property7, - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - await Assert.That(list).Count().IsGreaterThan(0); - } - - [Test] - public async Task WhenAnyValue_7Props_Tuple_Str_Dist() - { - var vm = new TestViewModel(); - var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property8, + x => x.Property9, + x => x.Property10, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_7Props_Sel() + public async Task WhenAnyValue_10Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + x => x.Property10, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_7Props_Sel_Str() + public async Task WhenAnyValue_10Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( + vm.WhenAnyValue( nameof(TestViewModel.Property1), nameof(TestViewModel.Property2), nameof(TestViewModel.Property3), @@ -1080,12 +1725,38 @@ public async Task WhenAnyValue_7Props_Sel_Str() nameof(TestViewModel.Property5), nameof(TestViewModel.Property6), nameof(TestViewModel.Property7), - (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_7Props_Sel_Dist() + public async Task WhenAnyValue_10Props_Sel_Str_Dist() + { + var vm = new TestViewModel(); + var list = new List(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + await Assert.That(list).Count().IsGreaterThan(0); + } + + [Test] + public async Task WhenAnyValue_11Props_Sel() { var vm = new TestViewModel(); var list = new List(); @@ -1097,17 +1768,44 @@ public async Task WhenAnyValue_7Props_Sel_Dist() x => x.Property5, x => x.Property6, x => x.Property7, - (v1, v2, v3, v4, v5, v6, v7) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property8, + x => x.Property9, + x => x.Property10, + x => x.Property11, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_7Props_Sel_Str_Dist() + public async Task WhenAnyValue_11Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + x => x.Property10, + x => x.Property11, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + await Assert.That(list).Count().IsGreaterThan(0); + } + + [Test] + public async Task WhenAnyValue_11Props_Sel_Str() + { + var vm = new TestViewModel(); + var list = new List(); + vm.WhenAnyValue( nameof(TestViewModel.Property1), nameof(TestViewModel.Property2), nameof(TestViewModel.Property3), @@ -1115,17 +1813,44 @@ public async Task WhenAnyValue_7Props_Sel_Str_Dist() nameof(TestViewModel.Property5), nameof(TestViewModel.Property6), nameof(TestViewModel.Property7), - (v1, v2, v3, v4, v5, v6, v7) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + nameof(TestViewModel.Property11), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_7Props_Sel() + public async Task WhenAnyValue_11Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + nameof(TestViewModel.Property11), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + await Assert.That(list).Count().IsGreaterThan(0); + } + + [Test] + public async Task WhenAnyValue_12Props_Sel() + { + var vm = new TestViewModel(); + var list = new List(); + vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -1133,16 +1858,46 @@ public async Task WhenAny_7Props_Sel() x => x.Property5, x => x.Property6, x => x.Property7, - (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property8, + x => x.Property9, + x => x.Property10, + x => x.Property11, + x => x.Property12, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_7Props_Sel_Str() + public async Task WhenAnyValue_12Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + x => x.Property10, + x => x.Property11, + x => x.Property12, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + await Assert.That(list).Count().IsGreaterThan(0); + } + + [Test] + public async Task WhenAnyValue_12Props_Sel_Str() + { + var vm = new TestViewModel(); + var list = new List(); + vm.WhenAnyValue( nameof(TestViewModel.Property1), nameof(TestViewModel.Property2), nameof(TestViewModel.Property3), @@ -1150,1776 +1905,1142 @@ public async Task WhenAny_7Props_Sel_Str() nameof(TestViewModel.Property5), nameof(TestViewModel.Property6), nameof(TestViewModel.Property7), - (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + nameof(TestViewModel.Property11), + nameof(TestViewModel.Property12), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_7Props_Sel_Dist() + public async Task WhenAnyValue_12Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - (v1, v2, v3, v4, v5, v6, v7) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + nameof(TestViewModel.Property10), + nameof(TestViewModel.Property11), + nameof(TestViewModel.Property12), + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_7Props_Sel_Str_Dist() + public async Task WhenAnyValue_1Prop_Expr() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - (v1, v2, v3, v4, v5, v6, v7) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAnyValue(x => x.Property1).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); + vm.Property1 = "a"; + await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyValue_8Props_Sel() + public async Task WhenAnyValue_1Prop_Expr_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAnyValue(x => x.Property1, true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_8Props_Sel_Str() + public async Task WhenAnyValue_1Prop_Str() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAnyValue(nameof(TestViewModel.Property1)).ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_8Props_Sel_Dist() + public async Task WhenAnyValue_1Prop_Str_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - (v1, v2, v3, v4, v5, v6, v7, v8) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List(); + vm.WhenAnyValue(nameof(TestViewModel.Property1), false) + .ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_8Props_Sel_Str_Dist() + public async Task WhenAnyValue_1Props_Sel() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - (v1, v2, v3, v4, v5, v6, v7, v8) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + Func selector = v1 => "x"; + vm.WhenAnyValue(x => x.Property1, selector).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_8Props_Sel() + public async Task WhenAnyValue_1Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + Func selector = v1 => "x"; + vm.WhenAnyValue(x => x.Property1, selector, true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_8Props_Sel_Str() + public async Task WhenAnyValue_1Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + v1 => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_8Props_Sel_Dist() + public async Task WhenAnyValue_1Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - (v1, v2, v3, v4, v5, v6, v7, v8) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + v1 => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_8Props_Sel_Str_Dist() + public async Task WhenAnyValue_2Props_Sel() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - (v1, v2, v3, v4, v5, v6, v7, v8) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_9Props_Sel() + public async Task WhenAnyValue_2Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + (v1, v2) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_9Props_Sel_Str() + public async Task WhenAnyValue_2Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_9Props_Sel_Dist() + public async Task WhenAnyValue_2Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + (v1, v2) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_9Props_Sel_Str_Dist() + public async Task WhenAnyValue_2Props_Tuple_Expr() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_9Props_Sel() + public async Task WhenAnyValue_2Props_Tuple_Expr_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_9Props_Sel_Str() + public async Task WhenAnyValue_2Props_Tuple_Str() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_9Props_Sel_Dist() + public async Task WhenAnyValue_2Props_Tuple_Str_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_9Props_Sel_Str_Dist() + public async Task WhenAnyValue_3Props_Sel() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_10Props_Sel() + public async Task WhenAnyValue_3Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + x => x.Property3, + (v1, v2, v3) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_10Props_Sel_Str() + public async Task WhenAnyValue_3Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_10Props_Sel_Dist() + public async Task WhenAnyValue_3Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + (v1, v2, v3) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_10Props_Sel_Str_Dist() + public async Task WhenAnyValue_3Props_Tuple_Expr() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_10Props_Sel() + public async Task WhenAnyValue_3Props_Tuple_Expr_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_10Props_Sel_Str() + public async Task WhenAnyValue_3Props_Tuple_Str() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_10Props_Sel_Dist() + public async Task WhenAnyValue_3Props_Tuple_Str_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_10Props_Sel_Str_Dist() + public async Task WhenAnyValue_4Props_Sel() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_11Props_Sel() + public async Task WhenAnyValue_4Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - x => x.Property11, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + (v1, v2, v3, v4) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_11Props_Sel_Str() + public async Task WhenAnyValue_4Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - nameof(TestViewModel.Property11), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_11Props_Sel_Dist() + public async Task WhenAnyValue_4Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - x => x.Property11, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + (v1, v2, v3, v4) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_11Props_Sel_Str_Dist() + public async Task WhenAnyValue_4Props_Tuple_Expr() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - nameof(TestViewModel.Property11), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_11Props_Sel() + public async Task WhenAnyValue_4Props_Tuple_Expr_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - x => x.Property11, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_11Props_Sel_Str() + public async Task WhenAnyValue_4Props_Tuple_Str() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - nameof(TestViewModel.Property11), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_11Props_Sel_Dist() + public async Task WhenAnyValue_4Props_Tuple_Str_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - x => x.Property11, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_11Props_Sel_Str_Dist() + public async Task WhenAnyValue_5Props_Sel() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - nameof(TestViewModel.Property11), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_12Props_Sel() + public async Task WhenAnyValue_5Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - x => x.Property11, - x => x.Property12, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + (v1, v2, v3, v4, v5) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_12Props_Sel_Str() + public async Task WhenAnyValue_5Props_Sel_Str() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - nameof(TestViewModel.Property11), - nameof(TestViewModel.Property12), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_12Props_Sel_Dist() + public async Task WhenAnyValue_5Props_Sel_Str_Dist() { var vm = new TestViewModel(); var list = new List(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + (v1, v2, v3, v4, v5) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + await Assert.That(list).Count().IsGreaterThan(0); + } + + [Test] + public async Task WhenAnyValue_5Props_Tuple_Expr() + { + var vm = new TestViewModel(); + var list = new List<(string?, string?, string?, string?, string?)>(); vm.WhenAnyValue( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - x => x.Property11, - x => x.Property12, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyValue_12Props_Sel_Str_Dist() + public async Task WhenAnyValue_5Props_Tuple_Expr_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAnyValue( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - nameof(TestViewModel.Property11), - nameof(TestViewModel.Property12), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_12Props_Sel() + public async Task WhenAnyValue_5Props_Tuple_Str() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - x => x.Property11, - x => x.Property12, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_12Props_Sel_Str() + public async Task WhenAnyValue_5Props_Tuple_Str_Dist() { var vm = new TestViewModel(); - var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - nameof(TestViewModel.Property11), - nameof(TestViewModel.Property12), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + var list = new List<(string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_12Props_Sel_Dist() + public async Task WhenAnyValue_6Props_Sel() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - x => x.Property1, - x => x.Property2, - x => x.Property3, - x => x.Property4, - x => x.Property5, - x => x.Property6, - x => x.Property7, - x => x.Property8, - x => x.Property9, - x => x.Property10, - x => x.Property11, - x => x.Property12, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAny_12Props_Sel_Str_Dist() + public async Task WhenAnyValue_6Props_Sel_Dist() { var vm = new TestViewModel(); var list = new List(); - vm.WhenAny( - nameof(TestViewModel.Property1), - nameof(TestViewModel.Property2), - nameof(TestViewModel.Property3), - nameof(TestViewModel.Property4), - nameof(TestViewModel.Property5), - nameof(TestViewModel.Property6), - nameof(TestViewModel.Property7), - nameof(TestViewModel.Property8), - nameof(TestViewModel.Property9), - nameof(TestViewModel.Property10), - nameof(TestViewModel.Property11), - nameof(TestViewModel.Property12), - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + (v1, v2, v3, v4, v5, v6) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_1Prop() + public async Task WhenAnyValue_6Props_Sel_Str() { var vm = new TestViewModel(); - var subj = new Subject(); - vm.ObservableProperty1 = subj; var list = new List(); - vm.WhenAnyObservable(x => x.ObservableProperty1).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj.OnNext("test"); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_2Props() + public async Task WhenAnyValue_6Props_Sel_Str_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + (v1, v2, v3, v4, v5, v6) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_2Props_Sel() + public async Task WhenAnyValue_6Props_Tuple_Expr() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - (v1, v2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); + var list = new List<(string?, string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_3Props() + public async Task WhenAnyValue_6Props_Tuple_Expr_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); + var list = new List<(string?, string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_3Props_Sel() + public async Task WhenAnyValue_6Props_Tuple_Str() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - (v1, v2, v3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); + var list = new List<(string?, string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_4Props() + public async Task WhenAnyValue_6Props_Tuple_Str_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); + var list = new List<(string?, string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_4Props_Sel() + public async Task WhenAnyValue_7Props_Sel() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - (v1, v2, v3, v4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_5Props() + public async Task WhenAnyValue_7Props_Sel_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + (v1, v2, v3, v4, v5, v6, v7) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_5Props_Sel() + public async Task WhenAnyValue_7Props_Sel_Str() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - (v1, v2, v3, v4, v5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_6Props() + public async Task WhenAnyValue_7Props_Sel_Str_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + (v1, v2, v3, v4, v5, v6, v7) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_6Props_Sel() + public async Task WhenAnyValue_7Props_Tuple_Expr() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - (v1, v2, v3, v4, v5, v6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); + var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_7Props() + public async Task WhenAnyValue_7Props_Tuple_Expr_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); + var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_7Props_Sel() + public async Task WhenAnyValue_7Props_Tuple_Str() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - (v1, v2, v3, v4, v5, v6, v7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); + var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7)).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_8Props() + public async Task WhenAnyValue_7Props_Tuple_Str_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; - var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); + var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_8Props_Sel() + public async Task WhenAnyValue_8Props_Sel() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8, - (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_9Props() + public async Task WhenAnyValue_8Props_Sel_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; - var subj9 = new Subject(); - vm.ObservableProperty9 = subj9; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8, - x => x.ObservableProperty9).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); - subj9.OnNext("test"); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + (v1, v2, v3, v4, v5, v6, v7, v8) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_9Props_Sel() + public async Task WhenAnyValue_8Props_Sel_Str() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; - var subj9 = new Subject(); - vm.ObservableProperty9 = subj9; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8, - x => x.ObservableProperty9, - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); - subj9.OnNext("test"); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + (v1, v2, v3, v4, v5, v6, v7, v8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_10Props() - { - var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; - var subj9 = new Subject(); - vm.ObservableProperty9 = subj9; - var subj10 = new Subject(); - vm.ObservableProperty10 = subj10; - var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8, - x => x.ObservableProperty9, - x => x.ObservableProperty10).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); - subj9.OnNext("test"); - subj10.OnNext("test"); - await Assert.That(list).Count().IsGreaterThan(0); - } - - [Test] - public async Task WhenAnyObservable_10Props_Sel() + public async Task WhenAnyValue_8Props_Sel_Str_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; - var subj9 = new Subject(); - vm.ObservableProperty9 = subj9; - var subj10 = new Subject(); - vm.ObservableProperty10 = subj10; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8, - x => x.ObservableProperty9, - x => x.ObservableProperty10, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); - subj9.OnNext("test"); - subj10.OnNext("test"); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + (v1, v2, v3, v4, v5, v6, v7, v8) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_11Props() + public async Task WhenAnyValue_9Props_Sel() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; - var subj9 = new Subject(); - vm.ObservableProperty9 = subj9; - var subj10 = new Subject(); - vm.ObservableProperty10 = subj10; - var subj11 = new Subject(); - vm.ObservableProperty11 = subj11; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8, - x => x.ObservableProperty9, - x => x.ObservableProperty10, - x => x.ObservableProperty11).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); - subj9.OnNext("test"); - subj10.OnNext("test"); - subj11.OnNext("test"); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_11Props_Sel() + public async Task WhenAnyValue_9Props_Sel_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; - var subj9 = new Subject(); - vm.ObservableProperty9 = subj9; - var subj10 = new Subject(); - vm.ObservableProperty10 = subj10; - var subj11 = new Subject(); - vm.ObservableProperty11 = subj11; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8, - x => x.ObservableProperty9, - x => x.ObservableProperty10, - x => x.ObservableProperty11, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); - subj9.OnNext("test"); - subj10.OnNext("test"); - subj11.OnNext("test"); + vm.WhenAnyValue( + x => x.Property1, + x => x.Property2, + x => x.Property3, + x => x.Property4, + x => x.Property5, + x => x.Property6, + x => x.Property7, + x => x.Property8, + x => x.Property9, + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_12Props() + public async Task WhenAnyValue_9Props_Sel_Str() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; - var subj9 = new Subject(); - vm.ObservableProperty9 = subj9; - var subj10 = new Subject(); - vm.ObservableProperty10 = subj10; - var subj11 = new Subject(); - vm.ObservableProperty11 = subj11; - var subj12 = new Subject(); - vm.ObservableProperty12 = subj12; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8, - x => x.ObservableProperty9, - x => x.ObservableProperty10, - x => x.ObservableProperty11, - x => x.ObservableProperty12).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); - subj9.OnNext("test"); - subj10.OnNext("test"); - subj11.OnNext("test"); - subj12.OnNext("test"); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } [Test] - public async Task WhenAnyObservable_12Props_Sel() + public async Task WhenAnyValue_9Props_Sel_Str_Dist() { var vm = new TestViewModel(); - var subj1 = new Subject(); - vm.ObservableProperty1 = subj1; - var subj2 = new Subject(); - vm.ObservableProperty2 = subj2; - var subj3 = new Subject(); - vm.ObservableProperty3 = subj3; - var subj4 = new Subject(); - vm.ObservableProperty4 = subj4; - var subj5 = new Subject(); - vm.ObservableProperty5 = subj5; - var subj6 = new Subject(); - vm.ObservableProperty6 = subj6; - var subj7 = new Subject(); - vm.ObservableProperty7 = subj7; - var subj8 = new Subject(); - vm.ObservableProperty8 = subj8; - var subj9 = new Subject(); - vm.ObservableProperty9 = subj9; - var subj10 = new Subject(); - vm.ObservableProperty10 = subj10; - var subj11 = new Subject(); - vm.ObservableProperty11 = subj11; - var subj12 = new Subject(); - vm.ObservableProperty12 = subj12; var list = new List(); - vm.WhenAnyObservable( - x => x.ObservableProperty1, - x => x.ObservableProperty2, - x => x.ObservableProperty3, - x => x.ObservableProperty4, - x => x.ObservableProperty5, - x => x.ObservableProperty6, - x => x.ObservableProperty7, - x => x.ObservableProperty8, - x => x.ObservableProperty9, - x => x.ObservableProperty10, - x => x.ObservableProperty11, - x => x.ObservableProperty12, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); - subj1.OnNext("test"); - subj2.OnNext("test"); - subj3.OnNext("test"); - subj4.OnNext("test"); - subj5.OnNext("test"); - subj6.OnNext("test"); - subj7.OnNext("test"); - subj8.OnNext("test"); - subj9.OnNext("test"); - subj10.OnNext("test"); - subj11.OnNext("test"); - subj12.OnNext("test"); + vm.WhenAnyValue( + nameof(TestViewModel.Property1), + nameof(TestViewModel.Property2), + nameof(TestViewModel.Property3), + nameof(TestViewModel.Property4), + nameof(TestViewModel.Property5), + nameof(TestViewModel.Property6), + nameof(TestViewModel.Property7), + nameof(TestViewModel.Property8), + nameof(TestViewModel.Property9), + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } private class TestViewModel : ReactiveObject { - private string? _property1; private IObservable? _observableProperty1; - private string? _property2; + private IObservable? _observableProperty10; + private IObservable? _observableProperty11; + private IObservable? _observableProperty12; private IObservable? _observableProperty2; - private string? _property3; private IObservable? _observableProperty3; - private string? _property4; private IObservable? _observableProperty4; - private string? _property5; private IObservable? _observableProperty5; - private string? _property6; private IObservable? _observableProperty6; - private string? _property7; private IObservable? _observableProperty7; - private string? _property8; private IObservable? _observableProperty8; - private string? _property9; private IObservable? _observableProperty9; + private string? _property1; private string? _property10; - private IObservable? _observableProperty10; private string? _property11; - private IObservable? _observableProperty11; private string? _property12; - private IObservable? _observableProperty12; - - public string? Property1 { get => _property1; set => this.RaiseAndSetIfChanged(ref _property1, value); } - - public IObservable? ObservableProperty1 { get => _observableProperty1; set => this.RaiseAndSetIfChanged(ref _observableProperty1, value); } - - public string? Property2 { get => _property2; set => this.RaiseAndSetIfChanged(ref _property2, value); } - - public IObservable? ObservableProperty2 { get => _observableProperty2; set => this.RaiseAndSetIfChanged(ref _observableProperty2, value); } - - public string? Property3 { get => _property3; set => this.RaiseAndSetIfChanged(ref _property3, value); } - - public IObservable? ObservableProperty3 { get => _observableProperty3; set => this.RaiseAndSetIfChanged(ref _observableProperty3, value); } - - public string? Property4 { get => _property4; set => this.RaiseAndSetIfChanged(ref _property4, value); } - - public IObservable? ObservableProperty4 { get => _observableProperty4; set => this.RaiseAndSetIfChanged(ref _observableProperty4, value); } - - public string? Property5 { get => _property5; set => this.RaiseAndSetIfChanged(ref _property5, value); } - - public IObservable? ObservableProperty5 { get => _observableProperty5; set => this.RaiseAndSetIfChanged(ref _observableProperty5, value); } - - public string? Property6 { get => _property6; set => this.RaiseAndSetIfChanged(ref _property6, value); } - - public IObservable? ObservableProperty6 { get => _observableProperty6; set => this.RaiseAndSetIfChanged(ref _observableProperty6, value); } - - public string? Property7 { get => _property7; set => this.RaiseAndSetIfChanged(ref _property7, value); } - - public IObservable? ObservableProperty7 { get => _observableProperty7; set => this.RaiseAndSetIfChanged(ref _observableProperty7, value); } - - public string? Property8 { get => _property8; set => this.RaiseAndSetIfChanged(ref _property8, value); } - - public IObservable? ObservableProperty8 { get => _observableProperty8; set => this.RaiseAndSetIfChanged(ref _observableProperty8, value); } - - public string? Property9 { get => _property9; set => this.RaiseAndSetIfChanged(ref _property9, value); } - - public IObservable? ObservableProperty9 { get => _observableProperty9; set => this.RaiseAndSetIfChanged(ref _observableProperty9, value); } - - public string? Property10 { get => _property10; set => this.RaiseAndSetIfChanged(ref _property10, value); } - - public IObservable? ObservableProperty10 { get => _observableProperty10; set => this.RaiseAndSetIfChanged(ref _observableProperty10, value); } - - public string? Property11 { get => _property11; set => this.RaiseAndSetIfChanged(ref _property11, value); } - - public IObservable? ObservableProperty11 { get => _observableProperty11; set => this.RaiseAndSetIfChanged(ref _observableProperty11, value); } - - public string? Property12 { get => _property12; set => this.RaiseAndSetIfChanged(ref _property12, value); } + private string? _property2; + private string? _property3; + private string? _property4; + private string? _property5; + private string? _property6; + private string? _property7; + private string? _property8; + private string? _property9; - public IObservable? ObservableProperty12 { get => _observableProperty12; set => this.RaiseAndSetIfChanged(ref _observableProperty12, value); } + public IObservable? ObservableProperty1 + { + get => _observableProperty1; + set => this.RaiseAndSetIfChanged(ref _observableProperty1, value); + } + + public IObservable? ObservableProperty10 + { + get => _observableProperty10; + set => this.RaiseAndSetIfChanged(ref _observableProperty10, value); + } + + public IObservable? ObservableProperty11 + { + get => _observableProperty11; + set => this.RaiseAndSetIfChanged(ref _observableProperty11, value); + } + + public IObservable? ObservableProperty12 + { + get => _observableProperty12; + set => this.RaiseAndSetIfChanged(ref _observableProperty12, value); + } + + public IObservable? ObservableProperty2 + { + get => _observableProperty2; + set => this.RaiseAndSetIfChanged(ref _observableProperty2, value); + } + + public IObservable? ObservableProperty3 + { + get => _observableProperty3; + set => this.RaiseAndSetIfChanged(ref _observableProperty3, value); + } + + public IObservable? ObservableProperty4 + { + get => _observableProperty4; + set => this.RaiseAndSetIfChanged(ref _observableProperty4, value); + } + + public IObservable? ObservableProperty5 + { + get => _observableProperty5; + set => this.RaiseAndSetIfChanged(ref _observableProperty5, value); + } + + public IObservable? ObservableProperty6 + { + get => _observableProperty6; + set => this.RaiseAndSetIfChanged(ref _observableProperty6, value); + } + + public IObservable? ObservableProperty7 + { + get => _observableProperty7; + set => this.RaiseAndSetIfChanged(ref _observableProperty7, value); + } + + public IObservable? ObservableProperty8 + { + get => _observableProperty8; + set => this.RaiseAndSetIfChanged(ref _observableProperty8, value); + } + + public IObservable? ObservableProperty9 + { + get => _observableProperty9; + set => this.RaiseAndSetIfChanged(ref _observableProperty9, value); + } + + public string? Property1 + { + get => _property1; + set => this.RaiseAndSetIfChanged(ref _property1, value); + } + + public string? Property10 + { + get => _property10; + set => this.RaiseAndSetIfChanged(ref _property10, value); + } + + public string? Property11 + { + get => _property11; + set => this.RaiseAndSetIfChanged(ref _property11, value); + } + + public string? Property12 + { + get => _property12; + set => this.RaiseAndSetIfChanged(ref _property12, value); + } + + public string? Property2 + { + get => _property2; + set => this.RaiseAndSetIfChanged(ref _property2, value); + } + + public string? Property3 + { + get => _property3; + set => this.RaiseAndSetIfChanged(ref _property3, value); + } + + public string? Property4 + { + get => _property4; + set => this.RaiseAndSetIfChanged(ref _property4, value); + } + + public string? Property5 + { + get => _property5; + set => this.RaiseAndSetIfChanged(ref _property5, value); + } + + public string? Property6 + { + get => _property6; + set => this.RaiseAndSetIfChanged(ref _property6, value); + } + + public string? Property7 + { + get => _property7; + set => this.RaiseAndSetIfChanged(ref _property7, value); + } + + public string? Property8 + { + get => _property8; + set => this.RaiseAndSetIfChanged(ref _property8, value); + } + + public string? Property9 + { + get => _property9; + set => this.RaiseAndSetIfChanged(ref _property9, value); + } } } diff --git a/src/tests/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs b/src/tests/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs index 043018fb60..1de16e9e23 100644 --- a/src/tests/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs +++ b/src/tests/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs @@ -8,19 +8,18 @@ namespace ReactiveUI.Tests; public class WaitForDispatcherSchedulerTests { /// - /// Tests call scheduler factory on creation. + /// Tests call scheduler factory on creation. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task CallSchedulerFactoryOnCreation() { var schedulerFactoryCalls = 0; - var schedulerFactory = new Func( - () => - { - schedulerFactoryCalls++; - return null!; - }); + var schedulerFactory = new Func(() => + { + schedulerFactoryCalls++; + return null!; + }); var sut = new WaitForDispatcherScheduler(schedulerFactory); @@ -28,9 +27,9 @@ public async Task CallSchedulerFactoryOnCreation() } /// - /// Calls that factories throws argument null exception falls back to current thread. + /// Calls that factories throws argument null exception falls back to current thread. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task FactoryThrowsArgumentNullException_FallsBackToCurrentThread() { @@ -38,30 +37,29 @@ public async Task FactoryThrowsArgumentNullException_FallsBackToCurrentThread() var schedulerFactory = new Func(() => throw new ArgumentNullException()); var sut = new WaitForDispatcherScheduler(schedulerFactory); sut.Schedule( - null!, - (scheduler, state) => - { - schedulerExecutedOn = scheduler; - return Disposable.Empty; - }); + null!, + (scheduler, state) => + { + schedulerExecutedOn = scheduler; + return Disposable.Empty; + }); await Assert.That(schedulerExecutedOn).IsEqualTo(CurrentThreadScheduler.Instance); } /// - /// Tests that factories throws exception re calls on schedule. + /// Tests that factories throws exception re calls on schedule. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task FactoryThrowsException_ReCallsOnSchedule() { var schedulerFactoryCalls = 0; - var schedulerFactory = new Func( - () => - { - schedulerFactoryCalls++; - throw new InvalidOperationException(); - }); + var schedulerFactory = new Func(() => + { + schedulerFactoryCalls++; + throw new InvalidOperationException(); + }); var sut = new WaitForDispatcherScheduler(schedulerFactory); sut.Schedule(() => { }); @@ -70,9 +68,9 @@ public async Task FactoryThrowsException_ReCallsOnSchedule() } /// - /// Tests that factories throws invalid operation exception falls back to current thread. + /// Tests that factories throws invalid operation exception falls back to current thread. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task FactoryThrowsInvalidOperationException_FallsBackToCurrentThread() { @@ -81,30 +79,29 @@ public async Task FactoryThrowsInvalidOperationException_FallsBackToCurrentThrea var sut = new WaitForDispatcherScheduler(schedulerFactory); sut.Schedule( - null!, - (scheduler, state) => - { - schedulerExecutedOn = scheduler; - return Disposable.Empty; - }); + null!, + (scheduler, state) => + { + schedulerExecutedOn = scheduler; + return Disposable.Empty; + }); await Assert.That(schedulerExecutedOn).IsEqualTo(CurrentThreadScheduler.Instance); } /// - /// Tests that factory uses cached scheduler. + /// Tests that factory uses cached scheduler. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task SuccessfulFactory_UsesCachedScheduler() { var schedulerFactoryCalls = 0; - var schedulerFactory = new Func( - () => - { - schedulerFactoryCalls++; - return CurrentThreadScheduler.Instance; - }); + var schedulerFactory = new Func(() => + { + schedulerFactoryCalls++; + return CurrentThreadScheduler.Instance; + }); var sut = new WaitForDispatcherScheduler(schedulerFactory); sut.Schedule(() => { }); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/HostTestFixture.cs b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/HostTestFixture.cs index 965872c2e2..f462a48cb0 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/HostTestFixture.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/HostTestFixture.cs @@ -3,10 +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. -namespace ReactiveUI.Tests; +using ReactiveUI.Tests.ReactiveObjects.Mocks; + +namespace ReactiveUI.Tests.WhenAny.Mockups; /// -/// A host test fixture. +/// A host test fixture. /// public class HostTestFixture : ReactiveObject { @@ -16,46 +18,44 @@ public class HostTestFixture : ReactiveObject private NonObservableTestFixture? _PocoChild; private int _SomeOtherParam; - public HostTestFixture() - { + public HostTestFixture() => _ownerName = this.WhenAnyValue(static x => x.Owner) - .WhereNotNull() - .Select(static owner => owner.WhenAnyValue(static x => x.Name)) - .Switch() - .ToProperty(this, static x => x.OwnerName); - } + .WhereNotNull() + .Select(static owner => owner.WhenAnyValue(static x => x.Name)) + .Switch() + .ToProperty(this, static x => x.OwnerName); /// - /// Gets the name of the owner. + /// Gets the name of the owner. /// /// - /// The name of the owner. + /// The name of the owner. /// public string? OwnerName => _ownerName.Value; /// - /// Gets or sets the owner. + /// Gets or sets the child. /// - /// - /// The owner. - /// - public OwnerClass? Owner + public TestFixture? Child { - get => _owner; - set => this.RaiseAndSetIfChanged(ref _owner, value); + get => _Child; + set => this.RaiseAndSetIfChanged(ref _Child, value); } /// - /// Gets or sets the child. + /// Gets or sets the owner. /// - public TestFixture? Child + /// + /// The owner. + /// + public OwnerClass? Owner { - get => _Child; - set => this.RaiseAndSetIfChanged(ref _Child, value); + get => _owner; + set => this.RaiseAndSetIfChanged(ref _owner, value); } /// - /// Gets or sets the poco child. + /// Gets or sets the poco child. /// public NonObservableTestFixture? PocoChild { @@ -64,7 +64,7 @@ public NonObservableTestFixture? PocoChild } /// - /// Gets or sets some other parameter. + /// Gets or sets some other parameter. /// public int SomeOtherParam { diff --git a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/NonObservableTestFixture.cs b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/NonObservableTestFixture.cs index f06eef1ec7..2100a55a65 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/NonObservableTestFixture.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/NonObservableTestFixture.cs @@ -3,7 +3,9 @@ // 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; +using ReactiveUI.Tests.ReactiveObjects.Mocks; + +namespace ReactiveUI.Tests.WhenAny.Mockups; public class NonObservableTestFixture { diff --git a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/NonReactiveINPCObject.cs b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/NonReactiveINPCObject.cs index 24a0b6bc0e..fc2d3bb97a 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/NonReactiveINPCObject.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/NonReactiveINPCObject.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. -namespace ReactiveUI.Tests; +using ReactiveUI.Tests.ReactiveObjects.Mocks; + +namespace ReactiveUI.Tests.WhenAny.Mockups; public class NonReactiveINPCObject : INotifyPropertyChanged { private TestFixture _inpcProperty = new(); - /// + /// public event PropertyChangedEventHandler? PropertyChanged; public TestFixture InpcProperty diff --git a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain1.cs b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain1.cs index 90797f9b1f..c3ca930b47 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain1.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain1.cs @@ -3,7 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.WhenAny.Mockups; public class ObjChain1 : ReactiveObject { diff --git a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain2.cs b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain2.cs index af22c0b856..89d937e262 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain2.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain2.cs @@ -3,7 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.WhenAny.Mockups; public class ObjChain2 : ReactiveObject { diff --git a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain3.cs b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain3.cs index 62708f5d60..0e5b546d3a 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain3.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/ObjChain3.cs @@ -3,7 +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. -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.WhenAny.Mockups; public class ObjChain3 : ReactiveObject { diff --git a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/OwnerClass.cs b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/OwnerClass.cs index 2d9daeb5c4..35aee88630 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/Mockups/OwnerClass.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/Mockups/OwnerClass.cs @@ -3,10 +3,10 @@ // 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; +namespace ReactiveUI.Tests.WhenAny.Mockups; /// -/// Owner Class. +/// Owner Class. /// /// public class OwnerClass : ReactiveObject @@ -14,10 +14,10 @@ public class OwnerClass : ReactiveObject private string? _name; /// - /// Gets or sets the name. + /// Gets or sets the name. /// /// - /// The name. + /// The name. /// public string? Name { diff --git a/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.cs b/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.cs index c62e8ac681..daae6088ee 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.cs @@ -4,56 +4,148 @@ // See the LICENSE file in the project root for full license information. using DynamicData; - -using Microsoft.Reactive.Testing; - -using ReactiveUI.Testing; - +using ReactiveUI.Tests.ReactiveObjects.Mocks; +using ReactiveUI.Tests.WhenAny.Mockups; using TUnit.Assertions.Enums; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.WhenAny; public class ReactiveNotifyPropertyChangedMixinTest { /// - /// Gets or sets the dummy. + /// Gets or sets the dummy. /// public string? Dummy { get; set; } /// - /// Verifies that any change in a deep expression list triggers the update sequence. + /// Verifies that any change in a deep expression list triggers the update sequence. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task AnyChangeInExpressionListTriggersUpdate() { var obj = new ObjChain1(); + var obsUpdated = false; obj.ObservableForProperty(x => x.Model.Model.Model.SomeOtherParam) - .Subscribe(_ => obsUpdated = true); + .Subscribe(_ => obsUpdated = true); obsUpdated = false; + obj.Model.Model.Model.SomeOtherParam = 42; + await Assert.That(obsUpdated).IsTrue(); obsUpdated = false; + obj.Model.Model.Model = new HostTestFixture(); + await Assert.That(obsUpdated).IsTrue(); obsUpdated = false; + obj.Model.Model = new ObjChain3 { Model = new HostTestFixture { SomeOtherParam = 10 } }; + await Assert.That(obsUpdated).IsTrue(); obsUpdated = false; + obj.Model = new ObjChain2(); + await Assert.That(obsUpdated).IsTrue(); } /// - /// Ensures multi-property expressions are correctly rewritten and resolved. + /// The Changed stream contains valid sender and property name data. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ChangedShouldHaveValidData() + { + var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz", PocoProperty = "Bamf" }; + + object? sender = null; + + string? propertyName = null; + + fixture.Changed.ObserveOn(ImmediateScheduler.Instance).Subscribe(x => + { + sender = x.Sender; + + propertyName = x.PropertyName; + }); + + fixture.UsesExprRaiseSet = "abc"; + + using (Assert.Multiple()) + { + await Assert.That(sender).IsEqualTo(fixture); + + await Assert.That(propertyName).IsEqualTo(nameof(fixture.UsesExprRaiseSet)); + } + + sender = null; + + propertyName = null; + + fixture.PocoProperty = "abc"; + + using (Assert.Multiple()) + { + await Assert.That(sender).IsNull(); + + await Assert.That(propertyName).IsNull(); + } + } + + /// + /// The Changing stream contains valid sender and property name data. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ChangingShouldHaveValidData() + { + var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz", PocoProperty = "Bamf" }; + + object? sender = null; + + string? propertyName = null; + + fixture.Changing.ObserveOn(ImmediateScheduler.Instance).Subscribe(x => + { + sender = x.Sender; + + propertyName = x.PropertyName; + }); + + fixture.UsesExprRaiseSet = "abc"; + + using (Assert.Multiple()) + { + await Assert.That(sender).IsEqualTo(fixture); + + await Assert.That(propertyName).IsEqualTo(nameof(fixture.UsesExprRaiseSet)); + } + + sender = null; + + propertyName = null; + + fixture.PocoProperty = "abc"; + + using (Assert.Multiple()) + { + await Assert.That(sender).IsNull(); + + await Assert.That(propertyName).IsNull(); + } + } + + /// + /// Ensures multi-property expressions are correctly rewritten and resolved. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task MultiPropertyExpressionsShouldBeProperlyResolved() { @@ -62,416 +154,1060 @@ public async Task MultiPropertyExpressionsShouldBeProperlyResolved() { static x => x!.Child!.IsOnlyOneWord!.Length, ["Child", "IsOnlyOneWord", "Length"] }, { static x => x.SomeOtherParam, ["SomeOtherParam"] }, { static x => x.Child!.IsNotNullString!, ["Child", "IsNotNullString"] }, - { static x => x.Child!.Changed, ["Child", "Changed"] }, + { static x => x.Child!.Changed, ["Child", "Changed"] } }; var dataTypes = new Dictionary>, string[]> { - { static x => x.Child!.IsOnlyOneWord!.Length, [typeof(TestFixture).FullName!, typeof(string).FullName!, typeof(int).FullName!] }, + { + static x => + x.Child!.IsOnlyOneWord!.Length, + [typeof(TestFixture).FullName!, typeof(string).FullName!, typeof(int).FullName!] + }, { static x => x.SomeOtherParam, [typeof(int).FullName!] }, { static x => x.Child!.IsNotNullString!, [typeof(TestFixture).FullName!, typeof(string).FullName!] }, { - static x => x.Child!.Changed, [typeof(TestFixture).FullName!, typeof(IObservable>).FullName!] - }, + static x => + x.Child!.Changed, + [ + typeof(TestFixture).FullName!, + typeof(IObservable>).FullName! + ] + } }; - var results = data.Keys.Select(static x => new { input = x, output = Reflection.Rewrite(x.Body).GetExpressionChain() }) - .ToArray(); + var results = data.Keys + .Select(static x => new { 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(); + + foreach (var x in results) + { + var names = x.output.Select(static y => + y.GetMemberInfo()?.Name ?? + throw new InvalidOperationException("propertyName should not be null.")).ToArray(); + + await Assert.That(names).IsEquivalentTo(data[x.input], CollectionOrdering.Matching); + } + + foreach (var x in resultTypes) + { + var types = x.output.Select(static y => y.Type.FullName!).ToArray(); + + await Assert.That(types).IsEquivalentTo(dataTypes[x.input], CollectionOrdering.Matching); + } + } + + /// + /// Non-nullable pipeline works without extra decorators. + /// + /// A representing the asynchronous operation. + [Test] + public async Task NonNullableTypesTestShouldntNeedDecorators() + { + var fixture = new WhenAnyTestFixture(); + + IEnumerable? result = null; + + 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); + + await Assert.That(result!.Count()).IsEqualTo(3); + } + + /// + /// Non-nullable tuple pipeline works without extra decorators. + /// + /// A representing the asynchronous operation. + [Test] + public async Task NonNullableTypesTestShouldntNeedDecorators2() + { + var fixture = new WhenAnyTestFixture(); + + IEnumerable? result = null; + + fixture.WhenAnyValue( + x => x.ProjectService.Projects, + x => x.AccountService.AccountUsers) + .Where(tuple => tuple.Item1?.Count > 0 && tuple.Item2?.Count > 0) + .Select(tuple => + { + var (_, users) = tuple; + + return users!.Values.Where(x => !string.IsNullOrWhiteSpace(x.LastName)); + }) + .Subscribe(dict => result = dict); + + await Assert.That(result!.Count()).IsEqualTo(3); + } + + /// + /// Nullable pipeline works without extra decorators. + /// + /// A representing the asynchronous operation. + [Test] + public async Task NullableTypesTestShouldntNeedDecorators() + { + var fixture = new WhenAnyTestFixture(); + + IEnumerable? result = null; + + 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); + + await Assert.That(result!.Count()).IsEqualTo(3); + } + + /// + /// Nullable tuple pipeline works without extra decorators. + /// + /// A representing the asynchronous operation. + [Test] + public async Task NullableTypesTestShouldntNeedDecorators2() + { + var fixture = new WhenAnyTestFixture(); + + IEnumerable? result = null; + + fixture.WhenAnyValue( + x => x.ProjectService.ProjectsNullable, + x => x.AccountService.AccountUsersNullable) + .Where(tuple => tuple.Item1.Count > 0 && tuple.Item2?.Count > 0) + .Select(tuple => + { + var (projects, users) = tuple; + + return users?.Values.Where(x => !string.IsNullOrWhiteSpace(x?.LastName)); + }) + .Subscribe(dict => result = dict); + + await Assert.That(result!.Count()).IsEqualTo(3); + } + + /// + /// Ensures intermediate objects are eligible for GC when property value changes. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ObjectShouldBeGarbageCollectedWhenPropertyValueChanges() + { + static (ObjChain1, WeakReference) GetWeakReference1() + { + var obj = new ObjChain1(); + + var weakRef = new WeakReference(obj.Model); + + obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); + + obj.Model = new ObjChain2(); + + return (obj, weakRef); + } + + static (ObjChain1, WeakReference) GetWeakReference2() + { + var obj = new ObjChain1(); + + var weakRef = new WeakReference(obj.Model.Model); + + obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); + + obj.Model.Model = new ObjChain3(); + + return (obj, weakRef); + } + + static (ObjChain1, WeakReference) GetWeakReference3() + { + var obj = new ObjChain1(); + + var weakRef = new WeakReference(obj.Model.Model.Model); + + obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); + + obj.Model.Model.Model = new HostTestFixture(); + + return (obj, weakRef); + } + + var (obj1, weakRef1) = GetWeakReference1(); + + var (obj2, weakRef2) = GetWeakReference2(); + + var (obj3, weakRef3) = GetWeakReference3(); + + GC.Collect(); + + GC.WaitForPendingFinalizers(); + + using (Assert.Multiple()) + { + await Assert.That(weakRef1.IsAlive).IsFalse(); + + await Assert.That(weakRef2.IsAlive).IsFalse(); + + await Assert.That(weakRef3.IsAlive).IsFalse(); + } + + // Keep objs alive till after GC (prevent JIT optimization) + GC.KeepAlive(obj1); + + GC.KeepAlive(obj2); + + GC.KeepAlive(obj3); + } + + /// + /// Tests ObservableForProperty with selector throws for null selector. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ObservableForProperty_NullSelector_Throws() + { + var fixture = new TestFixture(); + + await Assert.That(() => fixture.ObservableForProperty(x => x.IsOnlyOneWord, (Func)null!)) + .Throws(); + } + + /// + /// Tests ObservableForProperty string overload with property name. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task ObservableForProperty_StringPropertyName_ObservesProperty() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture(); + + var results = new List(); + + fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord)) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => results.Add(x.Value)); + + fixture.IsOnlyOneWord = "Value1"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); + + await Assert.That(results[0]).IsEqualTo("Value1"); + + fixture.IsOnlyOneWord = "Value2"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(2); + + await Assert.That(results[1]).IsEqualTo("Value2"); + } + + /// + /// Tests ObservableForProperty string overload with beforeChange. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task ObservableForProperty_StringPropertyNameBeforeChange_ObservesBeforeChange() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture { IsOnlyOneWord = "Initial" }; + + var results = new List(); + + fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord), true) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => results.Add(x.Value)); + + fixture.IsOnlyOneWord = "Changed"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); + + await Assert.That(results[0]).IsEqualTo("Initial"); + } + + /// + /// Tests ObservableForProperty string overload without skipInitial. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task ObservableForProperty_StringPropertyNameNoSkipInitial_EmitsInitialValue() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture { IsOnlyOneWord = "Initial" }; + + var results = new List(); + + fixture.ObservableForProperty( + nameof(TestFixture.IsOnlyOneWord), + false, + false) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => results.Add(x.Value)); + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); + + await Assert.That(results[0]).IsEqualTo("Initial"); + + fixture.IsOnlyOneWord = "Changed"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(2); + + await Assert.That(results[1]).IsEqualTo("Changed"); + } + + /// + /// Tests ObservableForProperty string overload throws for null property name. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ObservableForProperty_StringPropertyNameNull_Throws() + { + var fixture = new TestFixture(); + + await Assert.That(() => fixture.ObservableForProperty((string)null!)) + .Throws(); + } + + /// + /// Tests ObservableForProperty string overload throws for null item. + /// + /// A representing the asynchronous operation. + [Test] + public async Task ObservableForProperty_StringPropertyNameNullItem_Throws() + { + TestFixture? fixture = null; + + await Assert.That(() => fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord))) + .Throws(); + } + + /// + /// Tests ObservableForProperty string overload with isDistinct parameter. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task ObservableForProperty_StringPropertyNameWithIsDistinct_Works() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture(); + + var results = new List(); + + fixture.ObservableForProperty( + nameof(TestFixture.IsOnlyOneWord), + false, + true, + true) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => results.Add(x.Value)); + + fixture.IsOnlyOneWord = "Value1"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); + + fixture.IsOnlyOneWord = "Value2"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(2); + } + + /// + /// Tests ObservableForProperty with selector. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task ObservableForProperty_WithSelector_TransformsValues() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture { IsOnlyOneWord = "Test" }; + + var results = new List(); + + fixture.ObservableForProperty(x => x.IsOnlyOneWord, value => value?.Length ?? 0) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(results.Add); + + fixture.IsOnlyOneWord = "Hello"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); + + await Assert.That(results[0]).IsEqualTo(5); + + fixture.IsOnlyOneWord = "Hi"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(2); + + await Assert.That(results[1]).IsEqualTo(2); + } + + /// + /// Tests ObservableForProperty with selector and beforeChange. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task ObservableForProperty_WithSelectorAndBeforeChange_TransformsBeforeValues() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture { IsOnlyOneWord = "Initial" }; + + var results = new List(); + + fixture.ObservableForProperty(x => x.IsOnlyOneWord, value => value?.Length ?? 0, true) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(results.Add); + + fixture.IsOnlyOneWord = "Changed"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); + + await Assert.That(results[0]).IsEqualTo(7); // Length of "Initial" + + fixture.IsOnlyOneWord = "New"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(2); + + await Assert.That(results[1]).IsEqualTo(7); // Length of "Changed" + } + + /// + /// Verifies child change notification behavior when the host property changes. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPChangingTheHostPropertyShouldFireAChildChangeNotificationOnlyIfThePreviousChildIsDifferent() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new HostTestFixture { Child = new TestFixture() }; + + fixture.ObservableForProperty(static x => x.Child!.IsOnlyOneWord) + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + fixture.Child.IsOnlyOneWord = "Foo"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(1); + + fixture.Child.IsOnlyOneWord = "Bar"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + + fixture.Child = new TestFixture { IsOnlyOneWord = "Bar" }; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + } + + /// + /// Observes a named property and verifies notifications and values. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPNamedPropertyTest() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture(); + + fixture.ObservableForProperty(x => x.IsOnlyOneWord) + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + fixture.IsOnlyOneWord = "Foo"; + + await Assert.That(changes).Count().IsEqualTo(1); + + fixture.IsOnlyOneWord = "Bar"; + + await Assert.That(changes).Count().IsEqualTo(2); + + fixture.IsOnlyOneWord = "Baz"; + + await Assert.That(changes).Count().IsEqualTo(3); + + fixture.IsOnlyOneWord = "Baz"; + + await Assert.That(changes).Count().IsEqualTo(3); + + using (Assert.Multiple()) + { + await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); + + await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); + + await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", "Baz"]); + } + } + + /// + /// Observes a named property before change and verifies notifications. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPNamedPropertyTestBeforeChange() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture { IsOnlyOneWord = "Pre" }; + + fixture.ObservableForProperty( + x => x.IsOnlyOneWord, + true) + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + await Assert.That(changes).IsEmpty(); + + fixture.IsOnlyOneWord = "Foo"; + + await Assert.That(changes).Count().IsEqualTo(1); + + fixture.IsOnlyOneWord = "Bar"; + + await Assert.That(changes).Count().IsEqualTo(2); + + using (Assert.Multiple()) + { + await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); + + await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); + + await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Pre", "Foo"]); + } + } + + /// + /// Observes a named property with no initial-skip and verifies notifications. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPNamedPropertyTestNoSkipInitial() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture { IsOnlyOneWord = "Pre" }; + + fixture.ObservableForProperty( + x => x.IsOnlyOneWord, + false, + false) + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(1); + + fixture.IsOnlyOneWord = "Foo"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + + using (Assert.Multiple()) + { + await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); + + await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); + + await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Pre", "Foo"]); + } + } + + /// + /// Verifies that repeated values are de-duplicated. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPNamedPropertyTestRepeats() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture(); + + fixture.ObservableForProperty(x => x.IsOnlyOneWord) + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + fixture.IsOnlyOneWord = "Foo"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(1); + + fixture.IsOnlyOneWord = "Bar"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + + fixture.IsOnlyOneWord = "Bar"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + + fixture.IsOnlyOneWord = "Foo"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(3); + + using (Assert.Multiple()) + { + await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); + + await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); + + await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", "Foo"]); + } + } + + /// + /// Verifies re-subscription behavior when replacing the host. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPReplacingTheHostShouldResubscribeTheObservable() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new HostTestFixture { Child = new TestFixture() }; + + fixture.ObservableForProperty(x => x.Child!.IsOnlyOneWord) + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + fixture.Child.IsOnlyOneWord = "Foo"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(1); + + fixture.Child.IsOnlyOneWord = "Bar"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + + // From "Bar" to null (new TestFixture with null IsOnlyOneWord) + fixture.Child = new TestFixture(); + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(3); + + // Setting null again doesn't change + fixture.Child.IsOnlyOneWord = null!; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(3); + + fixture.Child.IsOnlyOneWord = "Baz"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(4); + + fixture.Child.IsOnlyOneWord = "Baz"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(4); + + using (Assert.Multiple()) + { + await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); + + await Assert.That(changes.All(x => x.GetPropertyName() == "Child.IsOnlyOneWord")).IsTrue(); + + await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", null, "Baz"]); + } + } + + /// + /// Verifies re-subscription behavior when host becomes null and then is restored. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPReplacingTheHostWithNullThenSettingItBackShouldResubscribeTheObservable() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new HostTestFixture { Child = new TestFixture() }; + + var fixtureProp = fixture.ObservableForProperty(x => x.Child!.IsOnlyOneWord); + + fixtureProp + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + fixture.Child.IsOnlyOneWord = "Foo"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(1); + + fixture.Child.IsOnlyOneWord = "Bar"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + + // Child becomes null + fixture.Child = null!; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + + // From "Bar" to null (child restored but value is null) + fixture.Child = new TestFixture(); + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(3); + + using (Assert.Multiple()) + { + await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); + + await Assert.That(changes.All(x => x.GetPropertyName() == "Child.IsOnlyOneWord")).IsTrue(); + + await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", null]); + } + } + + /// + /// Ensures ObservableForProperty works with non-reactive INPC objects. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPShouldWorkWithINPCObjectsToo() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new NonReactiveINPCObject { InpcProperty = null! }; + + fixture.ObservableForProperty(static x => x.InpcProperty.IsOnlyOneWord) + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + fixture.InpcProperty = new TestFixture(); + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(1); + + fixture.InpcProperty.IsOnlyOneWord = "Foo"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + + fixture.InpcProperty.IsOnlyOneWord = "Bar"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(3); + } + + /// + /// Simple child property observation test. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPSimpleChildPropertyTest() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new HostTestFixture { Child = new TestFixture() }; + + fixture.ObservableForProperty(x => x.Child!.IsOnlyOneWord) + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + fixture.Child.IsOnlyOneWord = "Foo"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(1); + + fixture.Child.IsOnlyOneWord = "Bar"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); + + fixture.Child.IsOnlyOneWord = "Baz"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(3); + + fixture.Child.IsOnlyOneWord = "Baz"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(3); + + using (Assert.Multiple()) + { + await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); + + await Assert.That(changes.All(x => x.GetPropertyName() == "Child.IsOnlyOneWord")).IsTrue(); + + await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", "Baz"]); + } + } + + /// + /// Simple property observation test. + /// + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task OFPSimplePropertyTest() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new TestFixture(); + + fixture.ObservableForProperty(x => x.IsOnlyOneWord) + .ToObservableChangeSet(ImmediateScheduler.Instance) + .Bind(out var changes) + .Subscribe(); + + fixture.IsOnlyOneWord = "Foo"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(1); - var resultTypes = dataTypes.Keys - .Select(static x => new - { - input = x, - output = Reflection.Rewrite(x.Body).GetExpressionChain() - }).ToArray(); + fixture.IsOnlyOneWord = "Bar"; - foreach (var x in results) - { - var names = x.output.Select(static y => - y.GetMemberInfo()?.Name ?? - throw new InvalidOperationException("propertyName should not be null.")).ToArray(); + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(2); - await Assert.That(names).IsEquivalentTo(data[x.input], CollectionOrdering.Matching); - } + fixture.IsOnlyOneWord = "Baz"; - foreach (var x in resultTypes) + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(3); + + fixture.IsOnlyOneWord = "Baz"; + + // ImmediateScheduler executes synchronously + await Assert.That(changes).Count().IsEqualTo(3); + + using (Assert.Multiple()) { - var types = x.output.Select(static y => y.Type.FullName!).ToArray(); - await Assert.That(types).IsEquivalentTo(dataTypes[x.input], CollectionOrdering.Matching); + await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); + + await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); + + await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", "Baz"]); } } /// - /// Verifies child change notification behavior when the host property changes. + /// Tests SubscribeToExpressionChain basic functionality. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OFPChangingTheHostPropertyShouldFireAChildChangeNotificationOnlyIfThePreviousChildIsDifferent() => - await new TestScheduler().With(static async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture() }; - fixture.ObservableForProperty(static x => x.Child!.IsOnlyOneWord) - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); - - fixture.Child.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); - - fixture.Child.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); - - fixture.Child = new TestFixture { IsOnlyOneWord = "Bar" }; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); - }); + [TestExecutor] + public async Task SubscribeToExpressionChain_BasicUsage_NotifiesOnChange() + { + var scheduler = TestContext.Current!.GetScheduler(); - /// - /// Observes a named property and verifies notifications and values. - /// - /// A representing the asynchronous operation. - [Test] - public async Task OFPNamedPropertyTest() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); - fixture.ObservableForProperty(x => x.IsOnlyOneWord) - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); + var fixture = new HostTestFixture { Child = new TestFixture() }; - fixture.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); + Expression> expression = x => x.Child!.IsOnlyOneWord; - fixture.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); + var results = new List(); - fixture.IsOnlyOneWord = "Baz"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); + fixture.SubscribeToExpressionChain(expression.Body) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => results.Add(x.Value)); - fixture.IsOnlyOneWord = "Baz"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); + fixture.Child.IsOnlyOneWord = "First"; - using (Assert.Multiple()) - { - await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); - await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); - await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", "Baz"]); - } - }); + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); - /// - /// Observes a named property before change and verifies notifications. - /// - /// A representing the asynchronous operation. - [Test] - public async Task OFPNamedPropertyTestBeforeChange() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture { IsOnlyOneWord = "Pre" }; - fixture.ObservableForProperty( - x => x.IsOnlyOneWord, - beforeChange: true) - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); - - scheduler.Start(); - await Assert.That(changes).IsEmpty(); - - fixture.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); - - fixture.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); - - using (Assert.Multiple()) - { - await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); - await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); - await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Pre", "Foo"]); - } - }); + await Assert.That(results[0]).IsEqualTo("First"); - /// - /// Observes a named property with no initial-skip and verifies notifications. - /// - /// A representing the asynchronous operation. - [Test] - public async Task OFPNamedPropertyTestNoSkipInitial() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture { IsOnlyOneWord = "Pre" }; - fixture.ObservableForProperty( - x => x.IsOnlyOneWord, - false, - false) - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); - - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); - - fixture.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); - - using (Assert.Multiple()) - { - await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); - await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); - await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Pre", "Foo"]); - } - }); + fixture.Child.IsOnlyOneWord = "Second"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(2); + + await Assert.That(results[1]).IsEqualTo("Second"); + } /// - /// Verifies that repeated values are de-duplicated. + /// Tests SubscribeToExpressionChain with beforeChange parameter. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OFPNamedPropertyTestRepeats() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); - fixture.ObservableForProperty(x => x.IsOnlyOneWord) - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); + [TestExecutor] + public async Task SubscribeToExpressionChain_WithBeforeChange_NotifiesBeforeChange() + { + var scheduler = TestContext.Current!.GetScheduler(); - fixture.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); + var fixture = new HostTestFixture { Child = new TestFixture { IsOnlyOneWord = "Initial" } }; - fixture.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); + Expression> expression = x => x.Child!.IsOnlyOneWord; - fixture.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); + var results = new List(); - fixture.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); + fixture.SubscribeToExpressionChain(expression.Body, true) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => results.Add(x.Value)); - using (Assert.Multiple()) - { - await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); - await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); - await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", "Foo"]); - } - }); + fixture.Child.IsOnlyOneWord = "Changed"; - /// - /// Verifies re-subscription behavior when replacing the host. - /// - /// A representing the asynchronous operation. - [Test] - public async Task OFPReplacingTheHostShouldResubscribeTheObservable() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture() }; - fixture.ObservableForProperty(x => x.Child!.IsOnlyOneWord) - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); - - fixture.Child.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); - - fixture.Child.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); - - // From "Bar" to null (new TestFixture with null IsOnlyOneWord) - fixture.Child = new TestFixture(); - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); - - // Setting null again doesn't change - fixture.Child.IsOnlyOneWord = null!; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); - - fixture.Child.IsOnlyOneWord = "Baz"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(4); - - fixture.Child.IsOnlyOneWord = "Baz"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(4); - - using (Assert.Multiple()) - { - await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); - await Assert.That(changes.All(x => x.GetPropertyName() == "Child.IsOnlyOneWord")).IsTrue(); - await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", null, "Baz"]); - } - }); + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); - /// - /// Verifies re-subscription behavior when host becomes null and then is restored. - /// - /// A representing the asynchronous operation. - [Test] - public async Task OFPReplacingTheHostWithNullThenSettingItBackShouldResubscribeTheObservable() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture() }; - var fixtureProp = fixture.ObservableForProperty(x => x.Child!.IsOnlyOneWord); - fixtureProp - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); - - fixture.Child.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); - - fixture.Child.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); - - // Child becomes null - fixture.Child = null!; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); - - // From "Bar" to null (child restored but value is null) - fixture.Child = new TestFixture(); - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); - - using (Assert.Multiple()) - { - await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); - await Assert.That(changes.All(x => x.GetPropertyName() == "Child.IsOnlyOneWord")).IsTrue(); - await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", null]); - } - }); + await Assert.That(results[0]).IsEqualTo("Initial"); + } /// - /// Ensures ObservableForProperty works with non-reactive INPC objects. + /// Tests SubscribeToExpressionChain with beforeChange and skipInitial. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OFPShouldWorkWithINPCObjectsToo() => - await new TestScheduler().With(static async scheduler => - { - var fixture = new NonReactiveINPCObject { InpcProperty = null! }; - fixture.ObservableForProperty(static x => x.InpcProperty.IsOnlyOneWord) - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); - - fixture.InpcProperty = new TestFixture(); - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); - - fixture.InpcProperty.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); - - fixture.InpcProperty.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); - }); + [TestExecutor] + public async Task SubscribeToExpressionChain_WithBeforeChangeAndSkipInitial_SkipsFirst() + { + var scheduler = TestContext.Current!.GetScheduler(); + + var fixture = new HostTestFixture { Child = new TestFixture { IsOnlyOneWord = "Initial" } }; + + Expression> expression = x => x.Child!.IsOnlyOneWord; + + var results = new List(); + + fixture.SubscribeToExpressionChain( + expression.Body, + true, + true) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => results.Add(x.Value)); + + // ImmediateScheduler executes synchronously + await Assert.That(results).IsEmpty(); + + fixture.Child.IsOnlyOneWord = "Changed"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); + + await Assert.That(results[0]).IsEqualTo("Initial"); + } /// - /// Simple child property observation test. + /// Tests SubscribeToExpressionChain with isDistinct parameter. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OFPSimpleChildPropertyTest() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture() }; - fixture.ObservableForProperty(x => x.Child!.IsOnlyOneWord) - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); + [TestExecutor] + public async Task SubscribeToExpressionChain_WithIsDistinct_Works() + { + var scheduler = TestContext.Current!.GetScheduler(); - fixture.Child.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); + var fixture = new HostTestFixture { Child = new TestFixture() }; - fixture.Child.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); + Expression> expression = x => x.Child!.IsOnlyOneWord; - fixture.Child.IsOnlyOneWord = "Baz"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); + var results = new List(); - fixture.Child.IsOnlyOneWord = "Baz"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); + fixture.SubscribeToExpressionChain( + expression.Body, + false, + true, + false, + true) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => results.Add(x.Value)); - using (Assert.Multiple()) - { - await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); - await Assert.That(changes.All(x => x.GetPropertyName() == "Child.IsOnlyOneWord")).IsTrue(); - await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", "Baz"]); - } - }); + fixture.Child.IsOnlyOneWord = "Value1"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); + + fixture.Child.IsOnlyOneWord = "Value2"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(2); + } /// - /// Simple property observation test. + /// Tests SubscribeToExpressionChain with suppressWarnings parameter. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task OFPSimplePropertyTest() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); - fixture.ObservableForProperty(x => x.IsOnlyOneWord) - .ToObservableChangeSet(ImmediateScheduler.Instance) - .Bind(out var changes) - .Subscribe(); + [TestExecutor] + public async Task SubscribeToExpressionChain_WithSuppressWarnings_DoesNotWarn() + { + var scheduler = TestContext.Current!.GetScheduler(); - fixture.IsOnlyOneWord = "Foo"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(1); + var fixture = new HostTestFixture { Child = new TestFixture() }; - fixture.IsOnlyOneWord = "Bar"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(2); + Expression> expression = x => x.Child!.IsOnlyOneWord; - fixture.IsOnlyOneWord = "Baz"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); + var results = new List(); - fixture.IsOnlyOneWord = "Baz"; - scheduler.Start(); - await Assert.That(changes).Count().IsEqualTo(3); + fixture.SubscribeToExpressionChain( + expression.Body, + false, + true, + true) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(x => results.Add(x.Value)); - using (Assert.Multiple()) - { - await Assert.That(changes.All(x => x.Sender == fixture)).IsTrue(); - await Assert.That(changes.All(x => x.GetPropertyName() == "IsOnlyOneWord")).IsTrue(); - await Assert.That(changes.Select(x => x.Value!)).IsEquivalentTo(["Foo", "Bar", "Baz"]); - } - }); + fixture.Child.IsOnlyOneWord = "Test"; + + // ImmediateScheduler executes synchronously + await Assert.That(results).Count().IsEqualTo(1); + + await Assert.That(results[0]).IsEqualTo("Test"); + } /// - /// Subscribing to WhenAny should push the current value. + /// Subscribing to WhenAny should push the current value. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task SubscriptionToWhenAnyShouldReturnCurrentValue() { var obj = new HostTestFixture(); + var observedValue = 1; + obj.WhenAnyValue(x => x.SomeOtherParam).Subscribe(x => observedValue = x); obj.SomeOtherParam = 42; @@ -480,973 +1216,888 @@ public async Task SubscriptionToWhenAnyShouldReturnCurrentValue() } /// - /// WhenAny executes on the current synchronization context. + /// WhenAny executes on the current synchronization context. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyShouldRunInContext() { var tid = Environment.CurrentManagedThreadId; + var whenAnyTid = 0; - await TaskPoolScheduler.Default.WithAsync(async _ => - { - var whenAnyTid = 0; - var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz", PocoProperty = "Bamf" }; + var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz", PocoProperty = "Bamf" }; - fixture.WhenAnyValue(x => x.IsNotNullString) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(__ => whenAnyTid = Environment.CurrentManagedThreadId); + fixture.WhenAnyValue(x => x.IsNotNullString) + .ObserveOn(ImmediateScheduler.Instance) + .Subscribe(__ => whenAnyTid = Environment.CurrentManagedThreadId); - fixture.IsNotNullString = "Bar"; + fixture.IsNotNullString = "Bar"; - await Assert.That(whenAnyTid).IsEqualTo(tid); - }); + await Assert.That(whenAnyTid).IsEqualTo(tid); } /// - /// WhenAny works with "normal" CLR properties. + /// WhenAny works with "normal" CLR properties. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyShouldWorkEvenWithNormalProperties() { var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz", PocoProperty = "Bamf" }; var output = new List?>(); + fixture.WhenAny( - static x => x.PocoProperty, - static x => x).Subscribe(output.Add); + static x => x.PocoProperty, + static x => x).Subscribe(output.Add); var output2 = new List(); + fixture.WhenAnyValue(static x => x.PocoProperty).Subscribe(output2.Add); var output3 = new List?>(); + fixture.WhenAny( - static x => x.NullableInt, - static x => x).Subscribe(output3.Add); + static x => x.NullableInt, + static x => x).Subscribe(output3.Add); var output4 = new List(); + fixture.WhenAnyValue(static x => x.NullableInt).Subscribe(output4.Add); using (Assert.Multiple()) { await Assert.That(output).Count().IsEqualTo(1); + await Assert.That(output[0]!.Sender).IsEqualTo(fixture); + await Assert.That(output[0]!.GetPropertyName()).IsEqualTo("PocoProperty"); + await Assert.That(output[0]!.Value).IsEqualTo("Bamf"); await Assert.That(output2).Count().IsEqualTo(1); + await Assert.That(output2[0]).IsEqualTo("Bamf"); await Assert.That(output3).Count().IsEqualTo(1); + await Assert.That(output3[0]!.Sender).IsEqualTo(fixture); + await Assert.That(output3[0]!.GetPropertyName()).IsEqualTo("NullableInt"); + await Assert.That(output3[0]!.Value).IsNull(); await Assert.That(output4).Count().IsEqualTo(1); + await Assert.That(output4[0]).IsNull(); } } /// - /// The Changed stream contains valid sender and property name data. + /// Smoke test for WhenAny combining two properties. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task ChangedShouldHaveValidData() + [TestExecutor] + public async Task WhenAnySmokeTest() { - var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz", PocoProperty = "Bamf" }; + var scheduler = TestContext.Current!.GetScheduler(); - object? sender = null; - string? propertyName = null; + var fixture = new HostTestFixture { Child = new TestFixture(), SomeOtherParam = 5 }; - fixture.Changed.ObserveOn(ImmediateScheduler.Instance).Subscribe(x => - { - sender = x.Sender; - propertyName = x.PropertyName; - }); + fixture.Child.IsNotNullString = "Foo"; - fixture.UsesExprRaiseSet = "abc"; + var output1 = new List>(); + + var output2 = new List>(); + + fixture.WhenAny( + x => x.SomeOtherParam, + x => x.Child!.IsNotNullString, + (sop, nns) => new { sop, nns }) + .Subscribe(x => + { + output1.Add(x!.sop); + output2.Add(x.nns!); + }); + + // ImmediateScheduler executes synchronously using (Assert.Multiple()) { - await Assert.That(sender).IsEqualTo(fixture); - await Assert.That(propertyName).IsEqualTo(nameof(fixture.UsesExprRaiseSet)); + await Assert.That(output1).Count().IsEqualTo(1); + + await Assert.That(output2).Count().IsEqualTo(1); + + await Assert.That(output1[0].Sender).IsEqualTo(fixture); + + await Assert.That(output2[0].Sender).IsEqualTo(fixture); + + await Assert.That(output1[0].Value).IsEqualTo(5); + + await Assert.That(output2[0].Value).IsEqualTo("Foo"); } - sender = null; - propertyName = null; - fixture.PocoProperty = "abc"; + fixture.SomeOtherParam = 10; + // ImmediateScheduler executes synchronously using (Assert.Multiple()) { - await Assert.That(sender).IsNull(); - await Assert.That(propertyName).IsNull(); - } - } + await Assert.That(output1).Count().IsEqualTo(2); - /// - /// The Changing stream contains valid sender and property name data. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ChangingShouldHaveValidData() - { - var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz", PocoProperty = "Bamf" }; + await Assert.That(output2).Count().IsEqualTo(2); - object? sender = null; - string? propertyName = null; + await Assert.That(output1[1].Sender).IsEqualTo(fixture); - fixture.Changing.ObserveOn(ImmediateScheduler.Instance).Subscribe(x => - { - sender = x.Sender; - propertyName = x.PropertyName; - }); + await Assert.That(output2[1].Sender).IsEqualTo(fixture); - fixture.UsesExprRaiseSet = "abc"; + await Assert.That(output1[1].Value).IsEqualTo(10); - using (Assert.Multiple()) - { - await Assert.That(sender).IsEqualTo(fixture); - await Assert.That(propertyName).IsEqualTo(nameof(fixture.UsesExprRaiseSet)); + await Assert.That(output2[1].Value).IsEqualTo("Foo"); } - sender = null; - propertyName = null; - fixture.PocoProperty = "abc"; + fixture.Child.IsNotNullString = "Bar"; + // ImmediateScheduler executes synchronously using (Assert.Multiple()) { - await Assert.That(sender).IsNull(); - await Assert.That(propertyName).IsNull(); - } - } + await Assert.That(output1).Count().IsEqualTo(3); - /// - /// Smoke test for WhenAny combining two properties. - /// - /// A representing the asynchronous operation. - [Test] - public async Task WhenAnySmokeTest() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture(), SomeOtherParam = 5 }; - fixture.Child.IsNotNullString = "Foo"; - - var output1 = new List>(); - var output2 = new List>(); - fixture.WhenAny( - x => x.SomeOtherParam, - x => x.Child!.IsNotNullString, - (sop, nns) => new { sop, nns }) - .Subscribe(x => - { - output1.Add(x!.sop); - output2.Add(x.nns!); - }); - - scheduler.Start(); - using (Assert.Multiple()) - { - await Assert.That(output1).Count().IsEqualTo(1); - await Assert.That(output2).Count().IsEqualTo(1); - await Assert.That(output1[0].Sender).IsEqualTo(fixture); - await Assert.That(output2[0].Sender).IsEqualTo(fixture); - await Assert.That(output1[0].Value).IsEqualTo(5); - await Assert.That(output2[0].Value).IsEqualTo("Foo"); - } + await Assert.That(output2).Count().IsEqualTo(3); - fixture.SomeOtherParam = 10; - scheduler.Start(); - using (Assert.Multiple()) - { - await Assert.That(output1).Count().IsEqualTo(2); - await Assert.That(output2).Count().IsEqualTo(2); - await Assert.That(output1[1].Sender).IsEqualTo(fixture); - await Assert.That(output2[1].Sender).IsEqualTo(fixture); - await Assert.That(output1[1].Value).IsEqualTo(10); - await Assert.That(output2[1].Value).IsEqualTo("Foo"); - } + await Assert.That(output1[2].Sender).IsEqualTo(fixture); - fixture.Child.IsNotNullString = "Bar"; - scheduler.Start(); - using (Assert.Multiple()) - { - await Assert.That(output1).Count().IsEqualTo(3); - await Assert.That(output2).Count().IsEqualTo(3); - await Assert.That(output1[2].Sender).IsEqualTo(fixture); - await Assert.That(output2[2].Sender).IsEqualTo(fixture); - await Assert.That(output1[2].Value).IsEqualTo(10); - await Assert.That(output2[2].Value).IsEqualTo("Bar"); - } - }); + await Assert.That(output2[2].Sender).IsEqualTo(fixture); + + await Assert.That(output1[2].Value).IsEqualTo(10); + + await Assert.That(output2[2].Value).IsEqualTo("Bar"); + } + } /// - /// WhenAnyValue supports normal CLR properties. + /// WhenAnyValue supports normal CLR properties. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueShouldWorkEvenWithNormalProperties() { var fixture = new TestFixture { IsNotNullString = "Foo", IsOnlyOneWord = "Baz", PocoProperty = "Bamf" }; var output1 = new List(); + var output2 = new List(); + fixture.WhenAnyValue(static x => x.PocoProperty).Subscribe(output1.Add); + fixture.WhenAnyValue( - static x => x.IsOnlyOneWord, - static x => x?.Length).Subscribe(output2.Add); + static x => x.IsOnlyOneWord, + static x => x?.Length).Subscribe(output2.Add); using (Assert.Multiple()) { await Assert.That(output1).Count().IsEqualTo(1); + await Assert.That(output1[0]).IsEqualTo("Bamf"); + await Assert.That(output2).Count().IsEqualTo(1); + await Assert.That(output2[0]).IsEqualTo(3); } } /// - /// Smoke test for WhenAnyValue combining two properties with a projector. + /// Smoke test for WhenAnyValue combining two properties with a projector. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task WhenAnyValueSmokeTest() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture(), SomeOtherParam = 5 }; - fixture.Child.IsNotNullString = "Foo"; - - var output1 = new List(); - var output2 = new List(); - fixture.WhenAnyValue( - x => x.SomeOtherParam, - x => x.Child!.IsNotNullString, - (sop, nns) => new { sop, nns }) - .Subscribe(x => - { - output1.Add(x!.sop); - output2.Add(x.nns!); - }); - - scheduler.Start(); - using (Assert.Multiple()) - { - await Assert.That(output1).Count().IsEqualTo(1); - await Assert.That(output2).Count().IsEqualTo(1); - await Assert.That(output1[0]).IsEqualTo(5); - await Assert.That(output2[0]).IsEqualTo("Foo"); - } + [TestExecutor] + public async Task WhenAnyValueSmokeTest() + { + var scheduler = TestContext.Current!.GetScheduler(); - fixture.SomeOtherParam = 10; - scheduler.Start(); - using (Assert.Multiple()) - { - await Assert.That(output1).Count().IsEqualTo(2); - await Assert.That(output2).Count().IsEqualTo(2); - await Assert.That(output1[1]).IsEqualTo(10); - await Assert.That(output2[1]).IsEqualTo("Foo"); - } + var fixture = new HostTestFixture { Child = new TestFixture(), SomeOtherParam = 5 }; + + fixture.Child.IsNotNullString = "Foo"; - fixture.Child.IsNotNullString = "Bar"; - scheduler.Start(); - using (Assert.Multiple()) + var output1 = new List(); + + var output2 = new List(); + + fixture.WhenAnyValue( + x => x.SomeOtherParam, + x => x.Child!.IsNotNullString, + (sop, nns) => new { sop, nns }) + .Subscribe(x => { - await Assert.That(output1).Count().IsEqualTo(3); - await Assert.That(output2).Count().IsEqualTo(3); - await Assert.That(output1[2]).IsEqualTo(10); - await Assert.That(output2[2]).IsEqualTo("Bar"); - } - }); + output1.Add(x!.sop); - /// - /// Ensures intermediate objects are eligible for GC when property value changes. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObjectShouldBeGarbageCollectedWhenPropertyValueChanges() - { - static (ObjChain1, WeakReference) GetWeakReference1() - { - var obj = new ObjChain1(); - var weakRef = new WeakReference(obj.Model); - obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); - obj.Model = new ObjChain2(); - return (obj, weakRef); - } + output2.Add(x.nns!); + }); - static (ObjChain1, WeakReference) GetWeakReference2() + // ImmediateScheduler executes synchronously + using (Assert.Multiple()) { - var obj = new ObjChain1(); - var weakRef = new WeakReference(obj.Model.Model); - obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); - obj.Model.Model = new ObjChain3(); - return (obj, weakRef); + await Assert.That(output1).Count().IsEqualTo(1); + + await Assert.That(output2).Count().IsEqualTo(1); + + await Assert.That(output1[0]).IsEqualTo(5); + + await Assert.That(output2[0]).IsEqualTo("Foo"); } - static (ObjChain1, WeakReference) GetWeakReference3() + fixture.SomeOtherParam = 10; + + // ImmediateScheduler executes synchronously + using (Assert.Multiple()) { - var obj = new ObjChain1(); - var weakRef = new WeakReference(obj.Model.Model.Model); - obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); - obj.Model.Model.Model = new HostTestFixture(); - return (obj, weakRef); - } + await Assert.That(output1).Count().IsEqualTo(2); - var (obj1, weakRef1) = GetWeakReference1(); - var (obj2, weakRef2) = GetWeakReference2(); - var (obj3, weakRef3) = GetWeakReference3(); + await Assert.That(output2).Count().IsEqualTo(2); - GC.Collect(); - GC.WaitForPendingFinalizers(); + await Assert.That(output1[1]).IsEqualTo(10); + await Assert.That(output2[1]).IsEqualTo("Foo"); + } + + fixture.Child.IsNotNullString = "Bar"; + + // ImmediateScheduler executes synchronously using (Assert.Multiple()) { - await Assert.That(weakRef1.IsAlive).IsFalse(); - await Assert.That(weakRef2.IsAlive).IsFalse(); - await Assert.That(weakRef3.IsAlive).IsFalse(); - } + await Assert.That(output1).Count().IsEqualTo(3); - // Keep objs alive till after GC (prevent JIT optimization) - GC.KeepAlive(obj1); - GC.KeepAlive(obj2); - GC.KeepAlive(obj3); - } + await Assert.That(output2).Count().IsEqualTo(3); - /// - /// Throws when WhenAnyValue receives an unsupported Equal expression. - /// - /// A representing the asynchronous operation. - [Test] - public async Task WhenAnyValueUnsupportedExpressionType_Equal() - { - var fixture = new TestFixture(); - var exception = - Assert.Throws(() => fixture.WhenAnyValue(x => x.IsNotNullString == x.IsOnlyOneWord) - .Subscribe()); + await Assert.That(output1[2]).IsEqualTo(10); - await Assert.That(exception!.Message).IsEqualTo("Unsupported expression of type 'Equal' (x.IsNotNullString == x.IsOnlyOneWord). Did you meant to use expressions 'x.IsNotNullString' and 'x.IsOnlyOneWord'?"); + await Assert.That(output2[2]).IsEqualTo("Bar"); + } } /// - /// Throws when WhenAnyValue receives an unsupported Constant expression. + /// Throws when WhenAnyValue receives an unsupported Constant expression. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueUnsupportedExpressionType_Constant() { var fixture = new TestFixture(); + var exception = Assert.Throws(() => fixture.WhenAnyValue(_ => Dummy).Subscribe()); - await Assert.That(exception!.Message).IsEqualTo("Unsupported expression of type 'Constant'. Did you miss the member access prefix in the expression?"); + await Assert.That(exception!.Message).IsEqualTo( + "Unsupported expression of type 'Constant'. Did you miss the member access prefix in the expression?"); } /// - /// Nullable pipeline works without extra decorators. + /// Throws when WhenAnyValue receives an unsupported Equal expression. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task NullableTypesTestShouldntNeedDecorators() + public async Task WhenAnyValueUnsupportedExpressionType_Equal() { - var fixture = new WhenAnyTestFixture(); - IEnumerable? result = null; - 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); + var fixture = new TestFixture(); - await Assert.That(result!.Count()).IsEqualTo(3); + var exception = + Assert.Throws(() => fixture.WhenAnyValue(x => x.IsNotNullString == x.IsOnlyOneWord) + .Subscribe()); + + await Assert.That(exception!.Message).IsEqualTo( + "Unsupported expression of type 'Equal' (x.IsNotNullString == x.IsOnlyOneWord). Did you meant to use expressions 'x.IsNotNullString' and 'x.IsOnlyOneWord'?"); } /// - /// Nullable tuple pipeline works without extra decorators. + /// WhenAnyValue with ten parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task NullableTypesTestShouldntNeedDecorators2() + public async Task WhenAnyValueWith10ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); - IEnumerable? result = null; + + string? result = null; + fixture.WhenAnyValue( - x => x.ProjectService.ProjectsNullable, - x => x.AccountService.AccountUsersNullable) - .Where(tuple => tuple.Item1.Count > 0 && tuple.Item2?.Count > 0) - .Select(tuple => - { - var (projects, users) = tuple; - return users?.Values.Where(x => !string.IsNullOrWhiteSpace(x?.LastName)); - }) - .Subscribe(dict => result = dict); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + x => x.Value6, + x => x.Value7, + x => x.Value8, + x => x.Value9, + x => x.Value10, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10)) + .Select(tuple => + { + var (value1, value2, value3, value4, value5, value6, value7, value8, value9, value10) = tuple; - await Assert.That(result!.Count()).IsEqualTo(3); + return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8 + value9 + value10; + }) + .Subscribe(value => result = value); + + await Assert.That(result).IsEqualTo("13579"); } /// - /// Non-nullable pipeline works without extra decorators. + /// WhenAnyValue with eleven parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task NonNullableTypesTestShouldntNeedDecorators() + public async Task WhenAnyValueWith11ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); - IEnumerable? result = null; - 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); - await Assert.That(result!.Count()).IsEqualTo(3); + string? result = null; + + fixture.WhenAnyValue( + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + x => x.Value6, + x => x.Value7, + x => x.Value8, + x => x.Value9, + x => x.Value10, + x => x.Value11, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11)) + .Select(tuple => + { + var (value1, value2, value3, value4, value5, value6, value7, value8, value9, value10, value11) = + tuple; + + return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8 + value9 + value10 + + value11; + }) + .Subscribe(value => result = value); + + await Assert.That(result).IsEqualTo("1357911"); } /// - /// Non-nullable tuple pipeline works without extra decorators. + /// WhenAnyValue with twelve parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] - public async Task NonNullableTypesTestShouldntNeedDecorators2() + public async Task WhenAnyValueWith12ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); - IEnumerable? result = null; + + string? result = null; + fixture.WhenAnyValue( - x => x.ProjectService.Projects, - x => x.AccountService.AccountUsers) - .Where(tuple => tuple.Item1?.Count > 0 && tuple.Item2?.Count > 0) - .Select(tuple => - { - var (_, users) = tuple; - return users!.Values.Where(x => !string.IsNullOrWhiteSpace(x.LastName)); - }) - .Subscribe(dict => result = dict); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + x => x.Value6, + x => x.Value7, + x => x.Value8, + x => x.Value9, + x => x.Value10, + x => x.Value11, + x => x.Value12, + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => + (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12)) + .Select(tuple => + { + var (value1, value2, value3, value4, value5, value6, value7, value8, value9, value10, value11, + value12) = tuple; - await Assert.That(result!.Count()).IsEqualTo(3); + return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8 + value9 + value10 + + value11 + value12; + }) + .Subscribe(value => result = value); + + await Assert.That(result).IsEqualTo("1357911"); } /// - /// WhenAnyValue with one parameter returns the value. + /// WhenAnyValue with one parameter returns the value. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith1Paramerters() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue(x => x.Value1).Subscribe(value => result = value); await Assert.That(result).IsEqualTo("1"); } /// - /// WhenAnyValue with one parameter reflects sequential changes (nullable target set later). + /// WhenAnyValue with one parameter reflects sequential changes (nullable target set later). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith1ParamertersSequentialCheck() { var fixture = new WhenAnyTestFixture(); - string? result = string.Empty; + + var result = string.Empty; + fixture.Value1 = null!; + fixture.WhenAnyValue(x => x.Value1).Subscribe(value => result = value); await Assert.That(result).IsNull(); fixture.Value1 = "A"; + await Assert.That(result).IsEqualTo("A"); fixture.Value1 = "B"; + await Assert.That(result).IsEqualTo("B"); fixture.Value1 = null!; + await Assert.That(result).IsNull(); } /// - /// WhenAnyValue with one parameter (already nullable) reflects sequential changes. + /// WhenAnyValue with one parameter (already nullable) reflects sequential changes. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith1ParamertersSequentialCheckNullable() { var fixture = new WhenAnyTestFixture(); - string? result = string.Empty; + + var result = string.Empty; + fixture.WhenAnyValue(x => x.Value2).Subscribe(value => result = value); await Assert.That(result).IsNull(); fixture.Value2 = "A"; + await Assert.That(result).IsEqualTo("A"); fixture.Value2 = "B"; + await Assert.That(result).IsEqualTo("B"); fixture.Value2 = null; + await Assert.That(result).IsNull(); } /// - /// WhenAnyValue with two parameters (tuple result). + /// WhenAnyValue with two parameters (tuple result). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith2ParamertersReturnsTuple() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2) - .Select(tuple => - { - var (value1, value2) = tuple; - return value1 + value2; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2) + .Select(tuple => + { + var (value1, value2) = tuple; + + return value1 + value2; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("1"); } /// - /// WhenAnyValue with two parameters (values projector). + /// WhenAnyValue with two parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith2ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - (v1, v2) => (v1, v2)) - .Select(tuple => - { - var (value1, value2) = tuple; - return value1 + value2; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + (v1, v2) => (v1, v2)) + .Select(tuple => + { + var (value1, value2) = tuple; + + return value1 + value2; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("1"); } /// - /// WhenAnyValue with three parameters (tuple result). + /// WhenAnyValue with three parameters (tuple result). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith3ParamertersReturnsTuple() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3) - .Select(tuple => - { - var (value1, value2, value3) = tuple; - return value1 + value2 + value3; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3) + .Select(tuple => + { + var (value1, value2, value3) = tuple; + + return value1 + value2 + value3; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("13"); } /// - /// WhenAnyValue with three parameters (values projector). + /// WhenAnyValue with three parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith3ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - (v1, v2, v3) => (v1, v2, v3)) - .Select(tuple => - { - var (value1, value2, value3) = tuple; - return value1 + value2 + value3; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + (v1, v2, v3) => (v1, v2, v3)) + .Select(tuple => + { + var (value1, value2, value3) = tuple; + + return value1 + value2 + value3; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("13"); } /// - /// WhenAnyValue with four parameters (tuple result). + /// WhenAnyValue with four parameters (tuple result). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith4ParamertersReturnsTuple() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4) - .Select(tuple => - { - var (value1, value2, value3, value4) = tuple; - return value1 + value2 + value3 + value4; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4) + .Select(tuple => + { + var (value1, value2, value3, value4) = tuple; + + return value1 + value2 + value3 + value4; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("13"); } /// - /// WhenAnyValue with four parameters (values projector). + /// WhenAnyValue with four parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith4ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - (v1, v2, v3, v4) => (v1, v2, v3, v4)) - .Select(tuple => - { - var (value1, value2, value3, value4) = tuple; - return value1 + value2 + value3 + value4; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + (v1, v2, v3, v4) => (v1, v2, v3, v4)) + .Select(tuple => + { + var (value1, value2, value3, value4) = tuple; + + return value1 + value2 + value3 + value4; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("13"); } /// - /// WhenAnyValue with five parameters (tuple result). + /// WhenAnyValue with five parameters (tuple result). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith5ParamertersReturnsTuple() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5) - .Select(tuple => - { - var (value1, value2, value3, value4, value5) = tuple; - return value1 + value2 + value3 + value4 + value5; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5) + .Select(tuple => + { + var (value1, value2, value3, value4, value5) = tuple; + + return value1 + value2 + value3 + value4 + value5; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("135"); } /// - /// WhenAnyValue with five parameters (values projector). + /// WhenAnyValue with five parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith5ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - (v1, v2, v3, v4, v5) => (v1, v2, v3, v4, v5)) - .Select(tuple => - { - var (value1, value2, value3, value4, value5) = tuple; - return value1 + value2 + value3 + value4 + value5; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + (v1, v2, v3, v4, v5) => (v1, v2, v3, v4, v5)) + .Select(tuple => + { + var (value1, value2, value3, value4, value5) = tuple; + + return value1 + value2 + value3 + value4 + value5; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("135"); } /// - /// WhenAnyValue with six parameters (tuple result). + /// WhenAnyValue with six parameters (tuple result). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith6ParamertersReturnsTuple() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - x => x.Value6) - .Select(tuple => - { - var (value1, value2, value3, value4, value5, value6) = tuple; - return value1 + value2 + value3 + value4 + value5 + value6; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + x => x.Value6) + .Select(tuple => + { + var (value1, value2, value3, value4, value5, value6) = tuple; + + return value1 + value2 + value3 + value4 + value5 + value6; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("135"); } /// - /// WhenAnyValue with six parameters (values projector). + /// WhenAnyValue with six parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith6ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - x => x.Value6, - (v1, v2, v3, v4, v5, v6) => (v1, v2, v3, v4, v5, v6)) - .Select(tuple => - { - var (value1, value2, value3, value4, value5, value6) = tuple; - return value1 + value2 + value3 + value4 + value5 + value6; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + x => x.Value6, + (v1, v2, v3, v4, v5, v6) => (v1, v2, v3, v4, v5, v6)) + .Select(tuple => + { + var (value1, value2, value3, value4, value5, value6) = tuple; + + return value1 + value2 + value3 + value4 + value5 + value6; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("135"); } /// - /// WhenAnyValue with seven parameters (tuple result). + /// WhenAnyValue with seven parameters (tuple result). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith7ParamertersReturnsTuple() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - x => x.Value6, - x => x.Value7) - .Select(tuple => - { - var (value1, value2, value3, value4, value5, value6, value7) = tuple; - return value1 + value2 + value3 + value4 + value5 + value6 + value7; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + x => x.Value6, + x => x.Value7) + .Select(tuple => + { + var (value1, value2, value3, value4, value5, value6, value7) = tuple; + + return value1 + value2 + value3 + value4 + value5 + value6 + value7; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("1357"); } /// - /// WhenAnyValue with seven parameters (values projector). + /// WhenAnyValue with seven parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith7ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - x => x.Value6, - x => x.Value7, - (v1, v2, v3, v4, v5, v6, v7) => (v1, v2, v3, v4, v5, v6, v7)) - .Select(tuple => - { - var (value1, value2, value3, value4, value5, value6, value7) = tuple; - return value1 + value2 + value3 + value4 + value5 + value6 + value7; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + x => x.Value6, + x => x.Value7, + (v1, v2, v3, v4, v5, v6, v7) => (v1, v2, v3, v4, v5, v6, v7)) + .Select(tuple => + { + var (value1, value2, value3, value4, value5, value6, value7) = tuple; + + return value1 + value2 + value3 + value4 + value5 + value6 + value7; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("1357"); } /// - /// WhenAnyValue with eight parameters (values projector). + /// WhenAnyValue with eight parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith8ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); + string? result = null; + fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - x => x.Value6, - x => x.Value7, - x => x.Value8, - (v1, v2, v3, v4, v5, v6, v7, v8) => (v1, v2, v3, v4, v5, v6, v7, v8)) - .Select(tuple => - { - var (value1, value2, value3, value4, value5, value6, value7, value8) = tuple; - return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8; - }) - .Subscribe(value => result = value); + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + x => x.Value6, + x => x.Value7, + x => x.Value8, + (v1, v2, v3, v4, v5, v6, v7, v8) => (v1, v2, v3, v4, v5, v6, v7, v8)) + .Select(tuple => + { + var (value1, value2, value3, value4, value5, value6, value7, value8) = tuple; + + return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8; + }) + .Subscribe(value => result = value); await Assert.That(result).IsEqualTo("1357"); } /// - /// WhenAnyValue with nine parameters (values projector). + /// WhenAnyValue with nine parameters (values projector). /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWith9ParamertersReturnsValues() { var fixture = new WhenAnyTestFixture(); - string? result = null; - fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - x => x.Value6, - x => x.Value7, - x => x.Value8, - x => x.Value9, - (v1, v2, v3, v4, v5, v6, v7, v8, v9) => (v1, v2, v3, v4, v5, v6, v7, v8, v9)) - .Select(tuple => - { - var (value1, value2, value3, value4, value5, value6, value7, value8, value9) = tuple; - return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8 + value9; - }) - .Subscribe(value => result = value); - - await Assert.That(result).IsEqualTo("13579"); - } - /// - /// WhenAnyValue with ten parameters (values projector). - /// - /// A representing the asynchronous operation. - [Test] - public async Task WhenAnyValueWith10ParamertersReturnsValues() - { - var fixture = new WhenAnyTestFixture(); string? result = null; - fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - x => x.Value6, - x => x.Value7, - x => x.Value8, - x => x.Value9, - x => x.Value10, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) => (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10)) - .Select(tuple => - { - var (value1, value2, value3, value4, value5, value6, value7, value8, value9, value10) = tuple; - return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8 + value9 + value10; - }) - .Subscribe(value => result = value); - - await Assert.That(result).IsEqualTo("13579"); - } - /// - /// WhenAnyValue with eleven parameters (values projector). - /// - /// A representing the asynchronous operation. - [Test] - public async Task WhenAnyValueWith11ParamertersReturnsValues() - { - var fixture = new WhenAnyTestFixture(); - string? result = null; fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - x => x.Value6, - x => x.Value7, - x => x.Value8, - x => x.Value9, - x => x.Value10, - x => x.Value11, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) => - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11)) - .Select(tuple => - { - var (value1, value2, value3, value4, value5, value6, value7, value8, value9, value10, value11) = - tuple; - return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8 + value9 + value10 + - value11; - }) - .Subscribe(value => result = value); - - await Assert.That(result).IsEqualTo("1357911"); - } + x => x.Value1, + x => x.Value2, + x => x.Value3, + x => x.Value4, + x => x.Value5, + x => x.Value6, + x => x.Value7, + x => x.Value8, + x => x.Value9, + (v1, v2, v3, v4, v5, v6, v7, v8, v9) => (v1, v2, v3, v4, v5, v6, v7, v8, v9)) + .Select(tuple => + { + var (value1, value2, value3, value4, value5, value6, value7, value8, value9) = tuple; - /// - /// WhenAnyValue with twelve parameters (values projector). - /// - /// A representing the asynchronous operation. - [Test] - public async Task WhenAnyValueWith12ParamertersReturnsValues() - { - var fixture = new WhenAnyTestFixture(); - string? result = null; - fixture.WhenAnyValue( - x => x.Value1, - x => x.Value2, - x => x.Value3, - x => x.Value4, - x => x.Value5, - x => x.Value6, - x => x.Value7, - x => x.Value8, - x => x.Value9, - x => x.Value10, - x => x.Value11, - x => x.Value12, - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) => - (v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12)) - .Select(tuple => - { - var (value1, value2, value3, value4, value5, value6, value7, value8, value9, value10, value11, - value12) = tuple; - return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8 + value9 + value10 + - value11 + value12; - }) - .Subscribe(value => result = value); + return value1 + value2 + value3 + value4 + value5 + value6 + value7 + value8 + value9; + }) + .Subscribe(value => result = value); - await Assert.That(result).IsEqualTo("1357911"); + await Assert.That(result).IsEqualTo("13579"); } /// - /// Verifies ToProperty projections for owner and owner name. + /// Verifies ToProperty projections for owner and owner name. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. [Test] public async Task WhenAnyValueWithToProperty() { @@ -1455,350 +2106,33 @@ public async Task WhenAnyValueWithToProperty() using (Assert.Multiple()) { await Assert.That(fixture.Owner).IsNull(); + await Assert.That(fixture.OwnerName).IsNull(); } - fixture.Owner = new() { Name = "Fred" }; + fixture.Owner = new OwnerClass { Name = "Fred" }; + using (Assert.Multiple()) { await Assert.That(fixture.Owner).IsNotNull(); + await Assert.That(fixture.OwnerName).IsEqualTo("Fred"); } fixture.Owner!.Name = "Wilma"; + await Assert.That(fixture.OwnerName).IsEqualTo("Wilma"); fixture.Owner.Name = null; + await Assert.That(fixture.OwnerName).IsNull(); fixture.Owner.Name = "Barney"; + await Assert.That(fixture.OwnerName).IsEqualTo("Barney"); fixture.Owner.Name = "Betty"; - await Assert.That(fixture.OwnerName).IsEqualTo("Betty"); - } - - /// - /// Tests ObservableForProperty with selector. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableForProperty_WithSelector_TransformsValues() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture { IsOnlyOneWord = "Test" }; - var results = new List(); - - fixture.ObservableForProperty(x => x.IsOnlyOneWord, value => value?.Length ?? 0) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(results.Add); - - fixture.IsOnlyOneWord = "Hello"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo(5); - - fixture.IsOnlyOneWord = "Hi"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(2); - await Assert.That(results[1]).IsEqualTo(2); - }); - - /// - /// Tests ObservableForProperty with selector and beforeChange. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableForProperty_WithSelectorAndBeforeChange_TransformsBeforeValues() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture { IsOnlyOneWord = "Initial" }; - var results = new List(); - - fixture.ObservableForProperty(x => x.IsOnlyOneWord, value => value?.Length ?? 0, beforeChange: true) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(results.Add); - - fixture.IsOnlyOneWord = "Changed"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo(7); // Length of "Initial" - - fixture.IsOnlyOneWord = "New"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(2); - await Assert.That(results[1]).IsEqualTo(7); // Length of "Changed" - }); - - /// - /// Tests ObservableForProperty with selector throws for null selector. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableForProperty_NullSelector_Throws() - { - var fixture = new TestFixture(); - - await Assert.That(() => fixture.ObservableForProperty(x => x.IsOnlyOneWord, (Func)null!)) - .Throws(); - } - - /// - /// Tests SubscribeToExpressionChain basic functionality. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SubscribeToExpressionChain_BasicUsage_NotifiesOnChange() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture() }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var results = new List(); - - fixture.SubscribeToExpressionChain(expression.Body) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => results.Add(x.Value)); - - fixture.Child.IsOnlyOneWord = "First"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo("First"); - - fixture.Child.IsOnlyOneWord = "Second"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(2); - await Assert.That(results[1]).IsEqualTo("Second"); - }); - - /// - /// Tests SubscribeToExpressionChain with beforeChange parameter. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SubscribeToExpressionChain_WithBeforeChange_NotifiesBeforeChange() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture { IsOnlyOneWord = "Initial" } }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var results = new List(); - - fixture.SubscribeToExpressionChain(expression.Body, beforeChange: true) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => results.Add(x.Value)); - - fixture.Child.IsOnlyOneWord = "Changed"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo("Initial"); - }); - - /// - /// Tests SubscribeToExpressionChain with beforeChange and skipInitial. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SubscribeToExpressionChain_WithBeforeChangeAndSkipInitial_SkipsFirst() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture { IsOnlyOneWord = "Initial" } }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var results = new List(); - - fixture.SubscribeToExpressionChain(expression.Body, beforeChange: true, skipInitial: true) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => results.Add(x.Value)); - - scheduler.Start(); - await Assert.That(results).IsEmpty(); - - fixture.Child.IsOnlyOneWord = "Changed"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo("Initial"); - }); - - /// - /// Tests SubscribeToExpressionChain with suppressWarnings parameter. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SubscribeToExpressionChain_WithSuppressWarnings_DoesNotWarn() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture() }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var results = new List(); - - fixture.SubscribeToExpressionChain( - expression.Body, - beforeChange: false, - skipInitial: true, - suppressWarnings: true) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => results.Add(x.Value)); - - fixture.Child.IsOnlyOneWord = "Test"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo("Test"); - }); - - /// - /// Tests SubscribeToExpressionChain with isDistinct parameter. - /// - /// A representing the asynchronous operation. - [Test] - public async Task SubscribeToExpressionChain_WithIsDistinct_Works() => - await new TestScheduler().With(async scheduler => - { - var fixture = new HostTestFixture { Child = new TestFixture() }; - Expression> expression = x => x.Child!.IsOnlyOneWord; - var results = new List(); - - fixture.SubscribeToExpressionChain( - expression.Body, - beforeChange: false, - skipInitial: true, - suppressWarnings: false, - isDistinct: true) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => results.Add(x.Value)); - - fixture.Child.IsOnlyOneWord = "Value1"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - - fixture.Child.IsOnlyOneWord = "Value2"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(2); - }); - - /// - /// Tests ObservableForProperty string overload with property name. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableForProperty_StringPropertyName_ObservesProperty() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); - var results = new List(); - - fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord)) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => results.Add(x.Value)); - - fixture.IsOnlyOneWord = "Value1"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo("Value1"); - - fixture.IsOnlyOneWord = "Value2"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(2); - await Assert.That(results[1]).IsEqualTo("Value2"); - }); - - /// - /// Tests ObservableForProperty string overload with beforeChange. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableForProperty_StringPropertyNameBeforeChange_ObservesBeforeChange() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture { IsOnlyOneWord = "Initial" }; - var results = new List(); - - fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord), beforeChange: true) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => results.Add(x.Value)); - - fixture.IsOnlyOneWord = "Changed"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo("Initial"); - }); - - /// - /// Tests ObservableForProperty string overload without skipInitial. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableForProperty_StringPropertyNameNoSkipInitial_EmitsInitialValue() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture { IsOnlyOneWord = "Initial" }; - var results = new List(); - - fixture.ObservableForProperty( - nameof(TestFixture.IsOnlyOneWord), - beforeChange: false, - skipInitial: false) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => results.Add(x.Value)); - - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - await Assert.That(results[0]).IsEqualTo("Initial"); - - fixture.IsOnlyOneWord = "Changed"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(2); - await Assert.That(results[1]).IsEqualTo("Changed"); - }); - - /// - /// Tests ObservableForProperty string overload with isDistinct parameter. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableForProperty_StringPropertyNameWithIsDistinct_Works() => - await new TestScheduler().With(async scheduler => - { - var fixture = new TestFixture(); - var results = new List(); - - fixture.ObservableForProperty( - nameof(TestFixture.IsOnlyOneWord), - beforeChange: false, - skipInitial: true, - isDistinct: true) - .ObserveOn(ImmediateScheduler.Instance) - .Subscribe(x => results.Add(x.Value)); - - fixture.IsOnlyOneWord = "Value1"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(1); - - fixture.IsOnlyOneWord = "Value2"; - scheduler.Start(); - await Assert.That(results).Count().IsEqualTo(2); - }); - - /// - /// Tests ObservableForProperty string overload throws for null property name. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableForProperty_StringPropertyNameNull_Throws() - { - var fixture = new TestFixture(); - - await Assert.That(() => fixture.ObservableForProperty((string)null!)) - .Throws(); - } - - /// - /// Tests ObservableForProperty string overload throws for null item. - /// - /// A representing the asynchronous operation. - [Test] - public async Task ObservableForProperty_StringPropertyNameNullItem_Throws() - { - TestFixture? fixture = null; - await Assert.That(() => fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord))) - .Throws(); + await Assert.That(fixture.OwnerName).IsEqualTo("Betty"); } } diff --git a/src/tests/ReactiveUI.NonParallel.Tests/WhenAny/TestWhenAnyObsViewModel.cs b/src/tests/ReactiveUI.Tests/WhenAny/TestWhenAnyObsViewModel.cs similarity index 80% rename from src/tests/ReactiveUI.NonParallel.Tests/WhenAny/TestWhenAnyObsViewModel.cs rename to src/tests/ReactiveUI.Tests/WhenAny/TestWhenAnyObsViewModel.cs index c36aec882d..255de938f6 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/WhenAny/TestWhenAnyObsViewModel.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/TestWhenAnyObsViewModel.cs @@ -6,7 +6,7 @@ using DynamicData; using DynamicData.Binding; -namespace ReactiveUI.Tests; +namespace ReactiveUI.Tests.WhenAny; public class TestWhenAnyObsViewModel : ReactiveObject { @@ -16,9 +16,15 @@ public class TestWhenAnyObsViewModel : ReactiveObject public TestWhenAnyObsViewModel() { - Command1 = ReactiveCommand.CreateFromObservable(Observable.Return, outputScheduler: ImmediateScheduler.Instance); - Command2 = ReactiveCommand.CreateFromObservable(Observable.Return, outputScheduler: ImmediateScheduler.Instance); - Command3 = ReactiveCommand.CreateFromObservable(Observable.Return, outputScheduler: ImmediateScheduler.Instance); + Command1 = ReactiveCommand.CreateFromObservable( + Observable.Return, + outputScheduler: ImmediateScheduler.Instance); + Command2 = ReactiveCommand.CreateFromObservable( + Observable.Return, + outputScheduler: ImmediateScheduler.Instance); + Command3 = ReactiveCommand.CreateFromObservable( + Observable.Return, + outputScheduler: ImmediateScheduler.Instance); } public IObservable>? Changes diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableTests.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableTests.cs new file mode 100644 index 0000000000..1471876660 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableTests.cs @@ -0,0 +1,303 @@ +// Copyright (c) 2025 .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 DynamicData; + +namespace ReactiveUI.Tests.WhenAny; + +/// +/// Tests for WhenAnyObservable functionality. +/// This test class is marked as NotInParallel because WhenAnyObservable relies on +/// the service locator (Locator.Current) to find ICreatesObservableForProperty implementations. +/// When tests run in parallel, they can interfere with each other's service locator state, +/// causing intermittent failures with "Could not find a ICreatesObservableForProperty" errors. +/// +[NotInParallel] +public class WhenAnyObservableTests +{ + /// + /// Tests that null observables do not cause exceptions. + /// + [Test] + public void NullObservablesDoNotCauseExceptions() + { + var fixture = new TestWhenAnyObsViewModel { Command1 = null }; + + // these are the 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( + static x => x.Command1, + 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, + static x => x.Command1).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + 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, + 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, + static x => x.Command1, + 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, + static x => x.Command1, + static x => x.Command1, + 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, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + 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, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + 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, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1).Subscribe(); + + // these are the overloads of WhenAnyObservable that perform a CombineLatest + fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static (zero, one) => Unit.Default) + .Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two) => Unit.Default).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two, three) => Unit.Default).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two, three, four) => Unit.Default).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two, three, four, five) => Unit.Default).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two, three, four, five, six) => Unit.Default).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two, three, four, five, six, seven) => Unit.Default).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two, three, four, five, six, seven, eight) => Unit.Default).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two, three, four, five, six, seven, eight, nine) => Unit.Default).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two, three, four, five, six, seven, eight, nine, ten) => Unit.Default).Subscribe(); + fixture.WhenAnyObservable( + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static x => x.Command1, + static (zero, one, two, three, four, five, six, seven, eight, nine, ten, eleven) => Unit.Default) + .Subscribe(); + } + + /// + /// Performs a smoke test on combining WhenAnyObservable. + /// + /// A task to monitor the progress. + [Test] + 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(ImmediateScheduler.Instance).Subscribe(list.Add); + await Assert.That(list).IsEmpty(); + + await fixture.Command1!.Execute(1); + await fixture.Command3.Execute("foo"); + await Assert.That(list).Count().IsEqualTo(1); + + await fixture.Command1.Execute(2); + await Assert.That(list).Count().IsEqualTo(2); + + await fixture.Command3.Execute("bar"); + using (Assert.Multiple()) + { + await Assert.That(list).Count().IsEqualTo(3); + + await Assert.That( + new[] { "foo : 1", "foo : 2", "bar : 2" }.Zip( + list, + static (expected, actual) => new { expected, actual }).All(static x => x.expected == x.actual)) + .IsTrue(); + } + } + + /// + /// Performs a smoke test testing WhenAnyObservable merging results. + /// + /// A task to monitor the progress. + [Test] + public async Task WhenAnyObservableSmokeTestMerging() + { + var fixture = new TestWhenAnyObsViewModel(); + + var list = new List(); + fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command2).ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); + await Assert.That(list).IsEmpty(); + + await fixture.Command1!.Execute(1); + await Assert.That(list).Count().IsEqualTo(1); + + await fixture.Command2.Execute(2); + await Assert.That(list).Count().IsEqualTo(2); + + await fixture.Command1.Execute(1); + using (Assert.Multiple()) + { + await Assert.That(list).Count().IsEqualTo(3); + + await Assert.That( + new[] { 1, 2, 1 }.Zip( + list, + static (expected, actual) => new { expected, actual }).All(static x => x.expected == x.actual)) + .IsTrue(); + } + } + + /// + /// Tests WhenAnyObservable with null object should update when object isnt null anymore. + /// + /// A representing the asynchronous operation. + [Test] + public async Task WhenAnyObservableWithNullObjectShouldUpdateWhenObjectIsntNullAnymore() + { + var fixture = new TestWhenAnyObsViewModel(); + fixture!.WhenAnyObservable(static x => x.Changes)!.Bind(out var output).ObserveOn(ImmediateScheduler.Instance) + .Subscribe(); + await Assert.That(output).IsEmpty(); + + fixture.MyListOfInts = []; + await Assert.That(output).IsEmpty(); + + fixture.MyListOfInts.Add(1); + await Assert.That(output).Count().IsEqualTo(1); + + fixture.MyListOfInts = null; + await Assert.That(output).Count().IsEqualTo(1); + } +} diff --git a/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.cs b/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.cs index 8bfcaa1f4c..83f8f53f24 100644 --- a/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.cs +++ b/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.cs @@ -3,161 +3,301 @@ // 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.Linq.Expressions; - namespace ReactiveUI.Tests; /// -/// Tests for WhenAnyDynamic methods in VariadicTemplates.cs. +/// Tests for WhenAnyDynamic methods in VariadicTemplates.cs. /// public class WhenAnyDynamicTest { [Test] - public async Task WhenAnyDynamic_1Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_10Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); + var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); + var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); + var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); + var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); + var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); + var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); + var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); + 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( property1, - (c1) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + property10, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_1Props_Selector_Distinct() + public async Task WhenAnyDynamic_10Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); + var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); + var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); + var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); + var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); + var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); + var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); + var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); + 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( - property1, - (c1) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + property10, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_2Props_Selector() + public async Task WhenAnyDynamic_10Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); + var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); + var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); + var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); + var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); + var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); + var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); + 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( - property1, - property2, - (c1, c2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + property10, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_2Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_11Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); + var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); + var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); + var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); + var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); + var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); + var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); + var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); + 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( property1, property2, - (c1, c2) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property3, + property4, + property5, + property6, + property7, + property8, + property9, + property10, + property11, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_2Props_Selector_Distinct() + public async Task WhenAnyDynamic_11Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); + var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); + var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); + var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); + var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); + var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); + var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); + var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); + 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( - property1, - property2, - (c1, c2) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + property10, + property11, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_3Props_Selector() + public async Task WhenAnyDynamic_11Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); + var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); + var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); + var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); + var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); + var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); + var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); + 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( - property1, - property2, - property3, - (c1, c2, c3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + property10, + property11, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_3Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_12Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); + var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); + var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); + var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); + var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); + var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); + var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); + var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); + 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( property1, property2, property3, - (c1, c2, c3) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property4, + property5, + property6, + property7, + property8, + property9, + property10, + property11, + property12, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => "x").ObserveOn(ImmediateScheduler.Instance) + .Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_3Props_Selector_Distinct() + public async Task WhenAnyDynamic_12Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); + var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); + var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); + var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); + var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); + var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); + var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); + var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); + 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( - property1, - property2, - property3, - (c1, c2, c3) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + property10, + property11, + property12, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_4Props_Selector() + public async Task WhenAnyDynamic_12Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -165,212 +305,181 @@ public async Task WhenAnyDynamic_4Props_Selector() var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); + var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); + var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); + var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); + var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); + var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); + var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); + 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( - property1, - property2, - property3, - property4, - (c1, c2, c3, c4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + property10, + property11, + property12, + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_4Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_1Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); - var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); - 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( - property1, - property2, - property3, - property4, - (c1, c2, c3, c4) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + c1 => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_4Props_Selector_Distinct() + public async Task WhenAnyDynamic_1Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); - var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); - 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( - property1, - property2, - property3, - property4, - (c1, c2, c3, c4) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + c1 => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_5Props_Selector() + public async Task WhenAnyDynamic_2Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); - var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); - 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( - property1, - property2, - property3, - property4, - property5, - (c1, c2, c3, c4, c5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + (c1, c2) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_5Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_2Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); - var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); - 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( - property1, - property2, - property3, - property4, - property5, - (c1, c2, c3, c4, c5) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + (c1, c2) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_5Props_Selector_Distinct() + public async Task WhenAnyDynamic_2Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); - var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); - 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( - property1, - property2, - property3, - property4, - property5, - (c1, c2, c3, c4, c5) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + (c1, c2) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_6Props_Selector() + public async Task WhenAnyDynamic_3Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); - var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - (c1, c2, c3, c4, c5, c6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + (c1, c2, c3) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_6Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_3Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); - var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - (c1, c2, c3, c4, c5, c6) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + (c1, c2, c3) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_6Props_Selector_Distinct() + public async Task WhenAnyDynamic_3Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); - var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - (c1, c2, c3, c4, c5, c6) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + (c1, c2, c3) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_7Props_Selector() + public async Task WhenAnyDynamic_4Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -378,26 +487,20 @@ public async Task WhenAnyDynamic_7Props_Selector() var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); - var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - (c1, c2, c3, c4, c5, c6, c7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + (c1, c2, c3, c4) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_7Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_4Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -405,27 +508,21 @@ public async Task WhenAnyDynamic_7Props_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 property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); - var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - (c1, c2, c3, c4, c5, c6, c7) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + (c1, c2, c3, c4) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_7Props_Selector_Distinct() + public async Task WhenAnyDynamic_4Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -433,27 +530,21 @@ public async Task WhenAnyDynamic_7Props_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 property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); - var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - (c1, c2, c3, c4, c5, c6, c7) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + (c1, c2, c3, c4) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_8Props_Selector() + public async Task WhenAnyDynamic_5Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -462,27 +553,21 @@ public async Task WhenAnyDynamic_8Props_Selector() var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); - var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - (c1, c2, c3, c4, c5, c6, c7, c8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + (c1, c2, c3, c4, c5) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_8Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_5Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -491,28 +576,22 @@ public async Task WhenAnyDynamic_8Props_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 property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); - var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - (c1, c2, c3, c4, c5, c6, c7, c8) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + (c1, c2, c3, c4, c5) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_8Props_Selector_Distinct() + public async Task WhenAnyDynamic_5Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -521,28 +600,22 @@ public async Task WhenAnyDynamic_8Props_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 property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); - var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - (c1, c2, c3, c4, c5, c6, c7, c8) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + (c1, c2, c3, c4, c5) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_9Props_Selector() + public async Task WhenAnyDynamic_6Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -552,28 +625,22 @@ public async Task WhenAnyDynamic_9Props_Selector() var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); - var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - (c1, c2, c3, c4, c5, c6, c7, c8, c9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + (c1, c2, c3, c4, c5, c6) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_9Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_6Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -583,29 +650,23 @@ public async Task WhenAnyDynamic_9Props_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 property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); - var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - (c1, c2, c3, c4, c5, c6, c7, c8, c9) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + (c1, c2, c3, c4, c5, c6) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_9Props_Selector_Distinct() + public async Task WhenAnyDynamic_6Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -615,29 +676,23 @@ public async Task WhenAnyDynamic_9Props_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 property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); - var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - (c1, c2, c3, c4, c5, c6, c7, c8, c9) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + (c1, c2, c3, c4, c5, c6) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_10Props_Selector() + public async Task WhenAnyDynamic_7Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -648,29 +703,23 @@ public async Task WhenAnyDynamic_10Props_Selector() var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); - var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - property10, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + (c1, c2, c3, c4, c5, c6, c7) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_10Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_7Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -681,30 +730,24 @@ public async Task WhenAnyDynamic_10Props_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 property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); - var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - property10, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + (c1, c2, c3, c4, c5, c6, c7) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_10Props_Selector_Distinct() + public async Task WhenAnyDynamic_7Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -715,30 +758,24 @@ public async Task WhenAnyDynamic_10Props_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 property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); - var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - property10, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + (c1, c2, c3, c4, c5, c6, c7) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_11Props_Selector() + public async Task WhenAnyDynamic_8Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -750,30 +787,24 @@ public async Task WhenAnyDynamic_11Props_Selector() var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); - var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - property10, - property11, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + (c1, c2, c3, c4, c5, c6, c7, c8) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_11Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_8Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -785,31 +816,25 @@ public async Task WhenAnyDynamic_11Props_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 property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); - var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - property10, - property11, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + (c1, c2, c3, c4, c5, c6, c7, c8) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_11Props_Selector_Distinct() + public async Task WhenAnyDynamic_8Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -821,31 +846,25 @@ public async Task WhenAnyDynamic_11Props_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 property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); - var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - property10, - property11, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + (c1, c2, c3, c4, c5, c6, c7, c8) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_12Props_Selector() + public async Task WhenAnyDynamic_9Props_Selector() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -858,31 +877,25 @@ public async Task WhenAnyDynamic_12Props_Selector() var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); - var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - property10, - property11, - property12, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + (c1, c2, c3, c4, c5, c6, c7, c8, c9) => "x").ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_12Props_Selector_NotDistinct() + public async Task WhenAnyDynamic_9Props_Selector_Distinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -895,32 +908,26 @@ public async Task WhenAnyDynamic_12Props_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 property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); - var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - property10, - property11, - property12, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => "x", - isDistinct: false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + (c1, c2, c3, c4, c5, c6, c7, c8, c9) => "x", + true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); } [Test] - public async Task WhenAnyDynamic_12Props_Selector_Distinct() + public async Task WhenAnyDynamic_9Props_Selector_NotDistinct() { var vm = new TestViewModel(); var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); @@ -933,25 +940,19 @@ public async Task WhenAnyDynamic_12Props_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 property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); - var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); - 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( - property1, - property2, - property3, - property4, - property5, - property6, - property7, - property8, - property9, - property10, - property11, - property12, - (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) => "x", - isDistinct: true).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); + property1, + property2, + property3, + property4, + property5, + property6, + property7, + property8, + property9, + (c1, c2, c3, c4, c5, c6, c7, c8, c9) => "x", + false).ObserveOn(ImmediateScheduler.Instance).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); @@ -961,6 +962,12 @@ private class TestViewModel : ReactiveObject { private string? _property1; + private string? _property10; + + private string? _property11; + + private string? _property12; + private string? _property2; private string? _property3; @@ -977,34 +984,76 @@ private class TestViewModel : ReactiveObject private string? _property9; - private string? _property10; - - private string? _property11; - - private string? _property12; - - public string? Property1 { get => _property1; set => this.RaiseAndSetIfChanged(ref _property1, value); } + public string? Property1 + { + get => _property1; + set => this.RaiseAndSetIfChanged(ref _property1, value); + } - public string? Property2 { get => _property2; set => this.RaiseAndSetIfChanged(ref _property2, value); } + public string? Property10 + { + get => _property10; + set => this.RaiseAndSetIfChanged(ref _property10, value); + } - public string? Property3 { get => _property3; set => this.RaiseAndSetIfChanged(ref _property3, value); } + public string? Property11 + { + get => _property11; + set => this.RaiseAndSetIfChanged(ref _property11, value); + } - public string? Property4 { get => _property4; set => this.RaiseAndSetIfChanged(ref _property4, value); } + public string? Property12 + { + get => _property12; + set => this.RaiseAndSetIfChanged(ref _property12, value); + } - public string? Property5 { get => _property5; set => this.RaiseAndSetIfChanged(ref _property5, value); } + public string? Property2 + { + get => _property2; + set => this.RaiseAndSetIfChanged(ref _property2, value); + } - public string? Property6 { get => _property6; set => this.RaiseAndSetIfChanged(ref _property6, value); } + public string? Property3 + { + get => _property3; + set => this.RaiseAndSetIfChanged(ref _property3, value); + } - public string? Property7 { get => _property7; set => this.RaiseAndSetIfChanged(ref _property7, value); } + public string? Property4 + { + get => _property4; + set => this.RaiseAndSetIfChanged(ref _property4, value); + } - public string? Property8 { get => _property8; set => this.RaiseAndSetIfChanged(ref _property8, value); } + public string? Property5 + { + get => _property5; + set => this.RaiseAndSetIfChanged(ref _property5, value); + } - public string? Property9 { get => _property9; set => this.RaiseAndSetIfChanged(ref _property9, value); } + public string? Property6 + { + get => _property6; + set => this.RaiseAndSetIfChanged(ref _property6, value); + } - public string? Property10 { get => _property10; set => this.RaiseAndSetIfChanged(ref _property10, value); } + public string? Property7 + { + get => _property7; + set => this.RaiseAndSetIfChanged(ref _property7, value); + } - public string? Property11 { get => _property11; set => this.RaiseAndSetIfChanged(ref _property11, value); } + public string? Property8 + { + get => _property8; + set => this.RaiseAndSetIfChanged(ref _property8, value); + } - public string? Property12 { get => _property12; set => this.RaiseAndSetIfChanged(ref _property12, value); } + public string? Property9 + { + get => _property9; + set => this.RaiseAndSetIfChanged(ref _property9, value); + } } } diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.Net4_7.verified.txt b/src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.Winforms.DotNet10_0.verified.txt similarity index 68% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.Net4_7.verified.txt rename to src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.Winforms.DotNet10_0.verified.txt index 07a25cafc1..e9f6c2e2b9 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.Net4_7.verified.txt +++ b/src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.Winforms.DotNet10_0.verified.txt @@ -1,4 +1,5 @@ -[assembly: System.Runtime.Versioning.TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName=".NET Framework 4.7.2")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.WinForms.Tests")] namespace ReactiveUI.Builder { public static class WinFormsReactiveUIBuilderExtensions @@ -21,12 +22,21 @@ namespace ReactiveUI.Winforms public ContentControlBindingHook() { } public bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction) { } } - public class CreatesWinformsCommandBinding : ReactiveUI.ICreatesCommandBinding + public sealed class CreatesWinformsCommandBinding : ReactiveUI.ICreatesCommandBinding { public CreatesWinformsCommandBinding() { } - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action addHandler, System.Action removeHandler) + where T : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs { } + public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } } public class PanelSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger { @@ -58,7 +68,7 @@ namespace ReactiveUI.Winforms public class Registrations : ReactiveUI.IWantsToRegisterStuff { public Registrations() { } - public void Register(System.Action, System.Type> registerFunction) { } + public void Register(ReactiveUI.IRegistrar registrar) { } } [System.ComponentModel.DefaultProperty("ViewModel")] public class RoutedControlHost : System.Windows.Forms.UserControl, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging @@ -89,6 +99,10 @@ namespace ReactiveUI.Winforms public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } } [System.ComponentModel.DefaultProperty("ViewModel")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewLocator.ResolveView uses reflection which is incompatible with AOT compilatio" + + "n.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This class uses reflection to determine view model types at runtime through ViewL" + + "ocator, which may be incompatible with trimming.")] public class ViewModelControlHost : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IReactiveObject, ReactiveUI.IViewFor, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { public ViewModelControlHost() { } @@ -127,7 +141,9 @@ namespace ReactiveUI.Winforms public class WinformsCreatesObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public WinformsCreatesObservableForProperty() { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } -} \ No newline at end of file +} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet10_0.verified.txt b/src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.Winforms.DotNet8_0.verified.txt similarity index 64% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet10_0.verified.txt rename to src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.Winforms.DotNet8_0.verified.txt index 0254b09eb6..e9f6c2e2b9 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet10_0.verified.txt +++ b/src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.Winforms.DotNet8_0.verified.txt @@ -1,4 +1,5 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.WinForms.Tests")] namespace ReactiveUI.Builder { public static class WinFormsReactiveUIBuilderExtensions @@ -13,37 +14,33 @@ namespace ReactiveUI.Winforms public class ActivationForViewFetcher : ReactiveUI.IActivationForViewFetcher, Splat.IEnableLogger { public ActivationForViewFetcher() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } public int GetAffinityForView(System.Type view) { } } public class ContentControlBindingHook : ReactiveUI.IPropertyBindingHook { public ContentControlBindingHook() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] public bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction) { } } - public class CreatesWinformsCommandBinding : ReactiveUI.ICreatesCommandBinding + public sealed class CreatesWinformsCommandBinding : ReactiveUI.ICreatesCommandBinding { public CreatesWinformsCommandBinding() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action addHandler, System.Action removeHandler) + where T : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs { } public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } } public class PanelSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger { public PanelSetMethodBindingConverter() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } } @@ -57,10 +54,6 @@ namespace ReactiveUI.Winforms public ReactiveUserControlNonGeneric() { } protected override void Dispose(bool disposing) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveUserControl provides base functionality for ReactiveUI which " + - "may require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveUserControl provides base functionality for ReactiveUI which " + - "may require unreferenced code")] public class ReactiveUserControl : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor where TViewModel : class { @@ -75,17 +68,11 @@ namespace ReactiveUI.Winforms public class Registrations : ReactiveUI.IWantsToRegisterStuff { public Registrations() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - public void Register(System.Action, System.Type> registerFunction) { } + public void Register(ReactiveUI.IRegistrar registrar) { } } [System.ComponentModel.DefaultProperty("ViewModel")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutedControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutedControlHost uses methods that may require unreferenced code")] public class RoutedControlHost : System.Windows.Forms.UserControl, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutedControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutedControlHost uses methods that may require unreferenced code")] public RoutedControlHost() { } [System.ComponentModel.Category("ReactiveUI")] [System.ComponentModel.Description("The default control when no viewmodel is specified")] @@ -108,18 +95,16 @@ namespace ReactiveUI.Winforms public class TableContentSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger { public TableContentSetMethodBindingConverter() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } } [System.ComponentModel.DefaultProperty("ViewModel")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewModelControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ViewModelControlHost uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewLocator.ResolveView uses reflection which is incompatible with AOT compilatio" + + "n.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This class uses reflection to determine view model types at runtime through ViewL" + + "ocator, which may be incompatible with trimming.")] public class ViewModelControlHost : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IReactiveObject, ReactiveUI.IViewFor, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewModelControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ViewModelControlHost uses methods that may require unreferenced code")] public ViewModelControlHost() { } [System.ComponentModel.Bindable(true)] [System.ComponentModel.Category("ReactiveUI")] @@ -153,18 +138,12 @@ namespace ReactiveUI.Winforms public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; protected override void Dispose(bool disposing) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WinformsCreatesObservableForProperty uses methods that require dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WinformsCreatesObservableForProperty uses methods that may require unreferenced c" + - "ode")] public class WinformsCreatesObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public WinformsCreatesObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } -} \ No newline at end of file +} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet9_0.verified.txt b/src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.Winforms.DotNet9_0.verified.txt similarity index 64% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet9_0.verified.txt rename to src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.Winforms.DotNet9_0.verified.txt index 0254b09eb6..e9f6c2e2b9 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/API/WinformsApiApprovalTests.Winforms.DotNet9_0.verified.txt +++ b/src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.Winforms.DotNet9_0.verified.txt @@ -1,4 +1,5 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.NonParallel.Tests")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.WinForms.Tests")] namespace ReactiveUI.Builder { public static class WinFormsReactiveUIBuilderExtensions @@ -13,37 +14,33 @@ namespace ReactiveUI.Winforms public class ActivationForViewFetcher : ReactiveUI.IActivationForViewFetcher, Splat.IEnableLogger { public ActivationForViewFetcher() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetActivationForView uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetActivationForView uses methods that may require unreferenced code")] public System.IObservable GetActivationForView(ReactiveUI.IActivatableView view) { } public int GetAffinityForView(System.Type view) { } } public class ContentControlBindingHook : ReactiveUI.IPropertyBindingHook { public ContentControlBindingHook() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ExecuteHook uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ExecuteHook uses methods that may require unreferenced code")] public bool ExecuteHook(object? source, object target, System.Func[]> getCurrentViewModelProperties, System.Func[]> getCurrentViewProperties, ReactiveUI.BindingDirection direction) { } } - public class CreatesWinformsCommandBinding : ReactiveUI.ICreatesCommandBinding + public sealed class CreatesWinformsCommandBinding : ReactiveUI.ICreatesCommandBinding { public CreatesWinformsCommandBinding() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public System.IDisposable BindCommandToObject(System.Windows.Input.ICommand? command, object? target, System.IObservable commandParameter, string eventName) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] - public int GetAffinityForObject(System.Type type, bool hasEventTarget) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action addHandler, System.Action removeHandler) + where T : class { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("String/reflection-based event binding may require members removed by trimming.")] + public System.IDisposable? BindCommandToObject(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, string eventName) + where T : class { } + public System.IDisposable? BindCommandToObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicEvents)] T, TEventArgs>(System.Windows.Input.ICommand? command, T? target, System.IObservable commandParameter, System.Action> addHandler, System.Action> removeHandler) + where T : class + where TEventArgs : System.EventArgs { } public int GetAffinityForObject<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] T>(bool hasEventTarget) { } } public class PanelSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger { public PanelSetMethodBindingConverter() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } } @@ -57,10 +54,6 @@ namespace ReactiveUI.Winforms public ReactiveUserControlNonGeneric() { } protected override void Dispose(bool disposing) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ReactiveUserControl provides base functionality for ReactiveUI which " + - "may require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ReactiveUserControl provides base functionality for ReactiveUI which " + - "may require unreferenced code")] public class ReactiveUserControl : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IViewFor, ReactiveUI.IViewFor where TViewModel : class { @@ -75,17 +68,11 @@ namespace ReactiveUI.Winforms public class Registrations : ReactiveUI.IWantsToRegisterStuff { public Registrations() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Register uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Register uses methods that may require unreferenced code")] - public void Register(System.Action, System.Type> registerFunction) { } + public void Register(ReactiveUI.IRegistrar registrar) { } } [System.ComponentModel.DefaultProperty("ViewModel")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutedControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutedControlHost uses methods that may require unreferenced code")] public class RoutedControlHost : System.Windows.Forms.UserControl, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("RoutedControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RoutedControlHost uses methods that may require unreferenced code")] public RoutedControlHost() { } [System.ComponentModel.Category("ReactiveUI")] [System.ComponentModel.Description("The default control when no viewmodel is specified")] @@ -108,18 +95,16 @@ namespace ReactiveUI.Winforms public class TableContentSetMethodBindingConverter : ReactiveUI.ISetMethodBindingConverter, Splat.IEnableLogger { public TableContentSetMethodBindingConverter() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObjects uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObjects uses methods that may require unreferenced code")] public int GetAffinityForObjects(System.Type? fromType, System.Type? toType) { } public object PerformSet(object? toTarget, object? newValue, object?[]? arguments) { } } [System.ComponentModel.DefaultProperty("ViewModel")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewModelControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ViewModelControlHost uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewLocator.ResolveView uses reflection which is incompatible with AOT compilatio" + + "n.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This class uses reflection to determine view model types at runtime through ViewL" + + "ocator, which may be incompatible with trimming.")] public class ViewModelControlHost : System.Windows.Forms.UserControl, ReactiveUI.IActivatableView, ReactiveUI.IReactiveObject, ReactiveUI.IViewFor, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("ViewModelControlHost uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ViewModelControlHost uses methods that may require unreferenced code")] public ViewModelControlHost() { } [System.ComponentModel.Bindable(true)] [System.ComponentModel.Category("ReactiveUI")] @@ -153,18 +138,12 @@ namespace ReactiveUI.Winforms public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; protected override void Dispose(bool disposing) { } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("WinformsCreatesObservableForProperty uses methods that require dynamic code gener" + - "ation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("WinformsCreatesObservableForProperty uses methods that may require unreferenced c" + - "ode")] public class WinformsCreatesObservableForProperty : ReactiveUI.ICreatesObservableForProperty, Splat.IEnableLogger { public WinformsCreatesObservableForProperty() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetAffinityForObject uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetAffinityForObject uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public int GetAffinityForObject(System.Type type, string propertyName, bool beforeChanged = false) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("GetNotificationForProperty uses methods that require dynamic code generation")] - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetNotificationForProperty uses methods that may require unreferenced code")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] public System.IObservable> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { } } -} \ No newline at end of file +} diff --git a/src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.cs b/src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.cs new file mode 100644 index 0000000000..8ccf955143 --- /dev/null +++ b/src/tests/ReactiveUI.WinForms.Tests/API/ApiApprovalTests.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2025 .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.Enums; + +namespace ReactiveUI.Tests.API; + +/// +/// Checks to make sure that the API is consistent with previous releases, and new API changes are highlighted. +/// +[ExcludeFromCodeCoverage] +[RunOn(OS.Windows)] +public class ApiApprovalTests +{ + /// + /// Generates public API for the ReactiveUI.Winforms API. + /// + /// A task to monitor the process. + [Test] + public Task Winforms() + { +#if WINDOWS + return typeof(ReactiveUI.Winforms.RoutedControlHost).Assembly.CheckApproval(["ReactiveUI"]); +#else + return Task.CompletedTask; +#endif + } +} diff --git a/src/tests/ReactiveUI.WinForms.Tests/AssemblyHooks.cs b/src/tests/ReactiveUI.WinForms.Tests/AssemblyHooks.cs new file mode 100644 index 0000000000..712b1c1122 --- /dev/null +++ b/src/tests/ReactiveUI.WinForms.Tests/AssemblyHooks.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2025 .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; +using ReactiveUI.Builder; +using Splat.Builder; +using TUnit.Core; + +namespace ReactiveUI.WinForms.Tests; + +/// +/// Assembly-level hooks for WinForms test initialization and cleanup. +/// +public static class AssemblyHooks +{ + /// + /// Called before any tests in this assembly start. + /// + [Before(Assembly)] + public static void AssemblySetup() + { + // Override ModeDetector to ensure we're detected as being in a unit test runner + ModeDetector.OverrideModeDetector(new TestModeDetector()); + + // Initialize ReactiveUI with WinForms services + var builder = RxAppBuilder.CreateReactiveUIBuilder(); + builder.WithWinForms().WithCoreServices().BuildApp(); + } + + /// + /// Called after all tests in this assembly complete. + /// + [After(Assembly)] + public static void AssemblyTeardown() + { + // Clean up resources + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + + /// + /// Mode detector that always indicates we're in a unit test runner. + /// + private sealed class TestModeDetector : IModeDetector + { + public bool? InUnitTestRunner() => true; + } +} diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IFooViewModel.cs b/src/tests/ReactiveUI.WinForms.Tests/AssemblyInfo.Parallel.cs similarity index 51% rename from src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IFooViewModel.cs rename to src/tests/ReactiveUI.WinForms.Tests/AssemblyInfo.Parallel.cs index 1293746db6..948bda91ae 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Locator/Mocks/IFooViewModel.cs +++ b/src/tests/ReactiveUI.WinForms.Tests/AssemblyInfo.Parallel.cs @@ -1,11 +1,8 @@ -// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Copyright (c) 2025 .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.Core; +using TUnit.Core; -/// -/// A interface view model. -/// -public interface IFooViewModel; +[assembly: NotInParallel] diff --git a/src/tests/ReactiveUI.NonParallel.Tests/ReactiveUI.NonParallel.Tests.csproj b/src/tests/ReactiveUI.WinForms.Tests/ReactiveUI.WinForms.Tests.csproj similarity index 52% rename from src/tests/ReactiveUI.NonParallel.Tests/ReactiveUI.NonParallel.Tests.csproj rename to src/tests/ReactiveUI.WinForms.Tests/ReactiveUI.WinForms.Tests.csproj index ccadebbe88..dee71ba23b 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/ReactiveUI.NonParallel.Tests.csproj +++ b/src/tests/ReactiveUI.WinForms.Tests/ReactiveUI.WinForms.Tests.csproj @@ -13,16 +13,17 @@ + + - - + - + - - + + - + @@ -31,12 +32,8 @@ - - - - - - + + @@ -46,43 +43,22 @@ - - - - - - + + + + - - - - - - - True - True - TestForm.resx - - - Form - - - True - True - TestFormNotCanActivate.resx - - - - - $(DefaultXamlRuntime) - - + + + + + @@ -99,5 +75,7 @@ + + diff --git a/src/tests/ReactiveUI.Builder.Tests/Platforms/WinForms/ReactiveUIBuilderWinFormsTests.cs b/src/tests/ReactiveUI.WinForms.Tests/ReactiveUIBuilderWinFormsTests.cs similarity index 100% rename from src/tests/ReactiveUI.Builder.Tests/Platforms/WinForms/ReactiveUIBuilderWinFormsTests.cs rename to src/tests/ReactiveUI.WinForms.Tests/ReactiveUIBuilderWinFormsTests.cs diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/ActivationTests.cs b/src/tests/ReactiveUI.WinForms.Tests/winforms/ActivationTests.cs similarity index 95% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/ActivationTests.cs rename to src/tests/ReactiveUI.WinForms.Tests/winforms/ActivationTests.cs index 4ea6b0f527..0439d7f5cb 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/ActivationTests.cs +++ b/src/tests/ReactiveUI.WinForms.Tests/winforms/ActivationTests.cs @@ -5,13 +5,15 @@ using System.Windows.Forms; -using TUnit.Core.Executors; +using ReactiveUI.WinForms.Tests.Winforms.Mocks; -namespace ReactiveUI.Tests.Winforms; +namespace ReactiveUI.WinForms.Tests.Winforms; /// /// Tests to make sure the activation works correctly. /// +[NotInParallel] +[TestExecutor] public class ActivationTests { /// @@ -19,7 +21,6 @@ public class ActivationTests /// /// A representing the asynchronous operation. [Test] - [TestExecutor] public async Task ActivationForViewFetcherSupportsDefaultWinformsComponents() { var target = new ReactiveUI.Winforms.ActivationForViewFetcher(); @@ -34,7 +35,6 @@ public async Task ActivationForViewFetcherSupportsDefaultWinformsComponents() /// /// A representing the asynchronous operation. [Test] - [TestExecutor] public async Task CanFetchActivatorForForm() { var form = new TestForm(); @@ -49,7 +49,6 @@ public async Task CanFetchActivatorForForm() /// /// A representing the asynchronous operation. [Test] - [TestExecutor] public async Task CanFetchActivatorForControl() { var control = new TestControl(); @@ -64,7 +63,6 @@ public async Task CanFetchActivatorForControl() /// /// A representing the asynchronous operation. [Test] - [TestExecutor] public async Task SmokeTestWindowsForm() { var target = new ReactiveUI.Winforms.ActivationForViewFetcher(); @@ -114,7 +112,6 @@ public async Task SmokeTestWindowsForm() /// /// A representing the asynchronous operation. [Test] - [TestExecutor] public async Task SmokeTestUserControl() { var target = new ReactiveUI.Winforms.ActivationForViewFetcher(); diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/CanActivateViewFetcherTests.cs b/src/tests/ReactiveUI.WinForms.Tests/winforms/CanActivateViewFetcherTests.cs similarity index 71% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/CanActivateViewFetcherTests.cs rename to src/tests/ReactiveUI.WinForms.Tests/winforms/CanActivateViewFetcherTests.cs index 60670b9ff5..d6b616ff5a 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/CanActivateViewFetcherTests.cs +++ b/src/tests/ReactiveUI.WinForms.Tests/winforms/CanActivateViewFetcherTests.cs @@ -3,51 +3,52 @@ // 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.Reactive.Testing; +using ReactiveUI.WinForms.Tests.Winforms.Mocks; -using ReactiveUI.Tests.Winforms; - -using TUnit.Core.Executors; - -namespace ReactiveUI.Tests; +namespace ReactiveUI.WinForms.Tests.Winforms; /// /// Tests to make sure the can activate view fetcher works correctly. /// +[NotInParallel] +[TestExecutor] public class CanActivateViewFetcherTests { /// /// Tests return negative for ICanActivate. /// + /// A representing the asynchronous operation. [Test] - [TestExecutor] - public void CanNotFetchActivatorForNonCanActivateableForm() + public async Task CanNotFetchActivatorForNonCanActivateableForm() { var form = new TestFormNotCanActivate(); var canActivateViewFetcher = new CanActivateViewFetcher(); - canActivateViewFetcher.GetActivationForView(form).AssertEqual(Observable.Return(false)); + var result = await canActivateViewFetcher.GetActivationForView(form).FirstAsync(); + await Assert.That(result).IsFalse(); } /// /// Tests return positive for ICanActivate. /// + /// A representing the asynchronous operation. [Test] - [TestExecutor] - public void CanGetActivationForViewForCanActivateableFormActivated() + public async Task CanGetActivationForViewForCanActivateableFormActivated() { var canActivateViewFetcher = new CanActivateViewFetcher(); - canActivateViewFetcher.GetActivationForView(new TestForm(1)).FirstAsync().AssertEqual(Observable.Return(true)); + var result = await canActivateViewFetcher.GetActivationForView(new TestForm(1)).FirstAsync(); + await Assert.That(result).IsTrue(); } /// /// Tests return negative for ICanActivate. /// + /// A representing the asynchronous operation. [Test] - [TestExecutor] - public void CanGetActivationForViewForCanActivateableFormDeactivated() + public async Task CanGetActivationForViewForCanActivateableFormDeactivated() { var canActivateViewFetcher = new CanActivateViewFetcher(); - canActivateViewFetcher.GetActivationForView(new TestForm(2)).FirstAsync().AssertEqual(Observable.Return(false)); + var result = await canActivateViewFetcher.GetActivationForView(new TestForm(2)).FirstAsync(); + await Assert.That(result).IsFalse(); } /// @@ -55,7 +56,6 @@ public void CanGetActivationForViewForCanActivateableFormDeactivated() /// /// A representing the asynchronous operation. [Test] - [TestExecutor] public async Task ReturnPositiveForICanActivate() { var canActivateViewFetcher = new CanActivateViewFetcher(); @@ -68,7 +68,6 @@ public async Task ReturnPositiveForICanActivate() /// /// A representing the asynchronous operation. [Test] - [TestExecutor] public async Task ReturnPositiveForICanActivateDerivatives() { var canActivateViewFetcher = new CanActivateViewFetcher(); @@ -81,7 +80,6 @@ public async Task ReturnPositiveForICanActivateDerivatives() /// /// A representing the asynchronous operation. [Test] - [TestExecutor] public async Task ReturnZeroForNonICanActivateDerivatives() { var canActivateViewFetcher = new CanActivateViewFetcher(); diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/CommandBindingImplementationTests.cs b/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingImplementationTests.cs similarity index 96% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/CommandBindingImplementationTests.cs rename to src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingImplementationTests.cs index 1728570df3..dbcda24e03 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/CommandBindingImplementationTests.cs +++ b/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingImplementationTests.cs @@ -4,14 +4,16 @@ // See the LICENSE file in the project root for full license information. using System.Windows.Forms; - +using ReactiveUI.WinForms.Tests.Winforms.Mocks; using TUnit.Core.Executors; -namespace ReactiveUI.Tests.Winforms; +namespace ReactiveUI.WinForms.Tests.Winforms; /// /// Checks the command bindings. /// +[NotInParallel] +[TestExecutor] public class CommandBindingImplementationTests { /// @@ -19,7 +21,6 @@ public class CommandBindingImplementationTests /// /// A representing the asynchronous operation. [Test] - [TestExecutor] public async Task CommandBindByNameWireup() { var vm = new WinformCommandBindViewModel(); @@ -44,7 +45,6 @@ public async Task CommandBindByNameWireup() } [Test] - [TestExecutor] public async Task CommandBindByNameWireupWithParameter() { var vm = new WinformCommandBindViewModel(); @@ -87,7 +87,6 @@ public async Task CommandBindByNameWireupWithParameter() } [Test] - [TestExecutor] public async Task CommandBindToExplicitEventWireupWithParameter() { var vm = new WinformCommandBindViewModel(); diff --git a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/CommandBindingTests.cs b/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingTests.cs similarity index 79% rename from src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/CommandBindingTests.cs rename to src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingTests.cs index 4d0cbdf149..8fa77e8575 100644 --- a/src/tests/ReactiveUI.NonParallel.Tests/Platforms/winforms/CommandBindingTests.cs +++ b/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingTests.cs @@ -4,17 +4,18 @@ // See the LICENSE file in the project root for full license information. using System.Windows.Forms; - using ReactiveUI.Winforms; -using TUnit.Core; +using ReactiveUI.WinForms.Tests.Winforms.Mocks; using TUnit.Core.Executors; -namespace ReactiveUI.Tests.Winforms; +namespace ReactiveUI.WinForms.Tests.Winforms; /// /// Command binding tests. /// [NotInParallel] +[TestExecutor] + public class CommandBindingTests { /// @@ -22,7 +23,6 @@ public class CommandBindingTests /// /// A representing the asynchronous unit test. [Test] - [TestExecutor] public async Task CommandBinderBindsToButtonAsync() { var fixture = new CreatesWinformsCommandBinding(); @@ -39,8 +39,8 @@ public async Task CommandBinderBindsToButtonAsync() using (Assert.Multiple()) { - await Assert.That(fixture.GetAffinityForObject(input.GetType(), true)).IsGreaterThan(0); - await Assert.That(fixture.GetAffinityForObject(input.GetType(), false)).IsGreaterThan(0); + await Assert.That(fixture.GetAffinityForObject