TypeConverterHelper.cs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. using System;
  2. using System.ComponentModel;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Globalization;
  5. using System.Linq;
  6. namespace Avalonia.Xaml.Interactivity;
  7. /// <summary>
  8. /// A helper class that enables converting values specified in markup (strings) to their object representation.
  9. /// </summary>
  10. [RequiresUnreferencedCode("This functionality is not compatible with trimming.")]
  11. internal static class TypeConverterHelper
  12. {
  13. /// <summary>
  14. /// Converts string representation of a value to its object representation.
  15. /// </summary>
  16. /// <param name="value">The value to convert.</param>
  17. /// <param name="destinationType">The destination type.</param>
  18. /// <returns>Object representation of the string value.</returns>
  19. /// <exception cref="ArgumentNullException">destinationType cannot be null.</exception>
  20. [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "DynamicallyAccessedMembers handles most of the problems.")]
  21. public static object? Convert(string value, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type destinationType)
  22. {
  23. if (destinationType is null)
  24. {
  25. throw new ArgumentNullException(nameof(destinationType));
  26. }
  27. var destinationTypeFullName = destinationType.FullName;
  28. if (destinationTypeFullName is null)
  29. {
  30. return null;
  31. }
  32. var scope = GetScope(destinationTypeFullName);
  33. // Value types in the "System" namespace must be special cased due to a bug in the xaml compiler
  34. if (string.Equals(scope, "System", StringComparison.Ordinal))
  35. {
  36. if (string.Equals(destinationTypeFullName, typeof(string).FullName, StringComparison.Ordinal))
  37. {
  38. return value;
  39. }
  40. if (string.Equals(destinationTypeFullName, typeof(bool).FullName, StringComparison.Ordinal))
  41. {
  42. return bool.Parse(value);
  43. }
  44. if (string.Equals(destinationTypeFullName, typeof(int).FullName, StringComparison.Ordinal))
  45. {
  46. return int.Parse(value, CultureInfo.InvariantCulture);
  47. }
  48. if (string.Equals(destinationTypeFullName, typeof(double).FullName, StringComparison.Ordinal))
  49. {
  50. return double.Parse(value, CultureInfo.InvariantCulture);
  51. }
  52. }
  53. try
  54. {
  55. if (destinationType.BaseType == typeof(Enum))
  56. return Enum.Parse(destinationType, value);
  57. if (destinationType.GetInterfaces().Any(t => t == typeof(IConvertible)))
  58. {
  59. return (value as IConvertible).ToType(destinationType, CultureInfo.InvariantCulture);
  60. }
  61. var converter = TypeDescriptor.GetConverter(destinationType);
  62. return converter.ConvertFromInvariantString(value);
  63. }
  64. catch (ArgumentException)
  65. {
  66. // not an enum
  67. }
  68. catch (InvalidCastException)
  69. {
  70. // not able to convert to anything
  71. }
  72. return null;
  73. }
  74. private static string GetScope(string name)
  75. {
  76. var indexOfLastPeriod = name.LastIndexOf('.');
  77. #if !NET6_0_OR_GREATER
  78. if (indexOfLastPeriod != name.Length - 1)
  79. {
  80. return name.Substring(0, indexOfLastPeriod);
  81. }
  82. return name;
  83. #else
  84. return indexOfLastPeriod != name.Length - 1 ? name[..indexOfLastPeriod] : name;
  85. #endif
  86. }
  87. }