From be914e4b2ed20924b0b1bbd0592dd6e48050d6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20Ko=C5=BEelj?= Date: Wed, 7 Dec 2022 17:12:28 +0100 Subject: [PATCH] [WIP] Refactor converters and dependant components. --- .editorconfig | 226 ++++++ Components/Autocomplete/Autocomplete.razor.cs | 2 +- Components/DatePicker/DateRange.cs | 1 + Components/Form/FormComponent.cs | 7 +- Components/Input/DebouncedInput.cs | 1 + Components/Input/RangeInput.razor.cs | 3 +- Components/NumericField/NumericField.razor.cs | 3 +- Components/Progress/ProgressLinear.razor.cs | 1 + Components/Select/Select.razor.cs | 2 +- Components/TimePicker/TimePicker.razor.cs | 3 +- Enums/InputType.cs | 78 +- Utilities/BindingConverters/Converter.cs | 170 ++-- Utilities/BindingConverters/Converters.cs | 56 +- Utilities/BindingConverters/DateConverter.cs | 117 +-- .../BindingConverters/DefaultConverter.cs | 761 +++++++++++------- .../BindingConverters/IsoDateConverter.cs | 17 + .../BindingConverters/NumericConverter.cs | 649 ++++++++------- Utilities/BindingConverters/RangeConverter.cs | 50 +- 18 files changed, 1243 insertions(+), 904 deletions(-) create mode 100644 .editorconfig create mode 100644 Utilities/BindingConverters/IsoDateConverter.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b4245b6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,226 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 3 +indent_style = tab +tab_width = 3 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = false +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = 0 + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion +dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = true:warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning + +# Expression-bodied members +csharp_style_expression_bodied_accessors = when_on_single_line:suggestion +csharp_style_expression_bodied_constructors = when_on_single_line:suggestion +csharp_style_expression_bodied_indexers = when_on_single_line:suggestion +csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion +csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion +csharp_style_expression_bodied_methods = when_on_single_line:suggestion +csharp_style_expression_bodied_operators = when_on_single_line:suggestion +csharp_style_expression_bodied_properties = when_on_single_line:suggestion + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true:suggestion +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async +csharp_style_prefer_readonly_struct = true + +# Code-block preferences +csharp_prefer_braces = when_multiline:suggestion +csharp_prefer_simple_using_statement = true +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_prefer_method_group_conversion = true:suggestion +csharp_style_prefer_top_level_statements = false:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:suggestion + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:suggestion +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = false:suggestion + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = no_change +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/Components/Autocomplete/Autocomplete.razor.cs b/Components/Autocomplete/Autocomplete.razor.cs index 30befb6..6f6f890 100644 --- a/Components/Autocomplete/Autocomplete.razor.cs +++ b/Components/Autocomplete/Autocomplete.razor.cs @@ -83,7 +83,7 @@ public partial class Autocomplete : InputBase, IDisposable _toStringFunc = value; - Converter = new LambdaConverter(_toStringFunc ?? (x => x?.ToString()), null); + SetConverter(new LambdaConverter(_toStringFunc ?? (x => x?.ToString()), null)); } } /// diff --git a/Components/DatePicker/DateRange.cs b/Components/DatePicker/DateRange.cs index a4e6b56..3c5d2aa 100644 --- a/Components/DatePicker/DateRange.cs +++ b/Components/DatePicker/DateRange.cs @@ -1,4 +1,5 @@ using Connected.Extensions; +using Connected.Utilities.BindingConverters; namespace Connected.Components; diff --git a/Components/Form/FormComponent.cs b/Components/Form/FormComponent.cs index fb4196e..bdecafb 100644 --- a/Components/Form/FormComponent.cs +++ b/Components/Form/FormComponent.cs @@ -68,12 +68,7 @@ public abstract class FormComponent : UIComponent, IFormComponent, IDispos /// /// The generic converter of the component. /// - [Parameter] - public Converter Converter - { - get => _converter; - set => SetConverter(value); - } + public Converter Converter => _converter; /// /// The culture of the component. Also sets the culture of the . diff --git a/Components/Input/DebouncedInput.cs b/Components/Input/DebouncedInput.cs index 10babcf..b899cfd 100644 --- a/Components/Input/DebouncedInput.cs +++ b/Components/Input/DebouncedInput.cs @@ -1,5 +1,6 @@ using System.Timers; using Connected.Annotations; +using Connected.Utilities.BindingConverters; using Microsoft.AspNetCore.Components; namespace Connected.Components; diff --git a/Components/Input/RangeInput.razor.cs b/Components/Input/RangeInput.razor.cs index f29abb8..85677ed 100644 --- a/Components/Input/RangeInput.razor.cs +++ b/Components/Input/RangeInput.razor.cs @@ -1,4 +1,5 @@ using Connected.Extensions; +using Connected.Utilities.BindingConverters; using Microsoft.AspNetCore.Components; namespace Connected.Components; @@ -10,7 +11,7 @@ public partial class RangeInput : InputBase> public RangeInput() { Value = new Range(); - Converter = new RangeConverter(); + SetConverter(new RangeConverter()); } protected string Classname => InputCssHelper.GetClassname(this, diff --git a/Components/NumericField/NumericField.razor.cs b/Components/NumericField/NumericField.razor.cs index ae9a3e6..0d42207 100644 --- a/Components/NumericField/NumericField.razor.cs +++ b/Components/NumericField/NumericField.razor.cs @@ -8,6 +8,7 @@ using System.Globalization; using Connected.Annotations; using Connected.Services; using Connected.Utilities; +using Connected.Utilities.BindingConverters; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; @@ -208,7 +209,7 @@ public partial class NumericField : DebouncedInput return (T)(object)Convert.ToInt64(FromInt64(Value) + FromInt64(Step) * factor); if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?)) return (T)(object)Convert.ToUInt64(FromUInt64(Value) + FromUInt64(Step) * factor); - return Num.To(Num.From(Value) + Num.From(Step) * factor); + return Number.To(Number.From(Value) + Number.From(Step) * factor); } /// diff --git a/Components/Progress/ProgressLinear.razor.cs b/Components/Progress/ProgressLinear.razor.cs index 5d9b3ee..3ecad86 100644 --- a/Components/Progress/ProgressLinear.razor.cs +++ b/Components/Progress/ProgressLinear.razor.cs @@ -2,6 +2,7 @@ using Connected.Annotations; using Connected.Extensions; using Connected.Utilities; +using Connected.Utilities.BindingConverters; using Microsoft.AspNetCore.Components; namespace Connected.Components; diff --git a/Components/Select/Select.razor.cs b/Components/Select/Select.razor.cs index 28c25ac..738b352 100644 --- a/Components/Select/Select.razor.cs +++ b/Components/Select/Select.razor.cs @@ -293,7 +293,7 @@ public partial class Select : InputBase, ISelect, IShadowSelect if (_toStringFunc == value) return; _toStringFunc = value; - Converter = new LambdaConverter(_toStringFunc ?? (x => x?.ToString()), null); + SetConverter(new LambdaConverter(_toStringFunc ?? (x => x?.ToString()), null)); } } diff --git a/Components/TimePicker/TimePicker.razor.cs b/Components/TimePicker/TimePicker.razor.cs index b4fa42c..f6c74fa 100644 --- a/Components/TimePicker/TimePicker.razor.cs +++ b/Components/TimePicker/TimePicker.razor.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using System.Dynamic; using System.Globalization; using System.Text.RegularExpressions; using Connected.Annotations; @@ -20,7 +19,7 @@ public partial class TimePicker : Picker { _timeFormat = format24Hours; - Converter = new LambdaConverter((e) => OnSet(e), (e) => OnGet(e)); + SetConverter(new LambdaConverter((e) => OnSet(e), (e) => OnGet(e))); AdornmentIcon = Icons.Material.Filled.AccessTime; AdornmentAriaLabel = "Open Time Picker"; diff --git a/Enums/InputType.cs b/Enums/InputType.cs index d5f852d..c385adc 100644 --- a/Enums/InputType.cs +++ b/Enums/InputType.cs @@ -1,38 +1,48 @@ using System.ComponentModel; -namespace Connected -{ - public enum InputType - { - [Description("text")] - Text, - [Description("password")] - Password, - [Description("email")] - Email, - [Description("hidden")] - Hidden, - [Description("number")] - Number, - [Description("search")] - Search, - [Description("tel")] - Telephone, - [Description("url")] - Url, - [Description("color")] - Color, - [Description("date")] - Date, - [Description("datetime-local")] - DateTimeLocal, - [Description("month")] - Month, - [Description("time")] - Time, - [Description("week")] - Week - +namespace Connected; - } +public enum InputType +{ + [Description("text")] + Text, + + [Description("password")] + Password, + + [Description("email")] + Email, + + [Description("hidden")] + Hidden, + + [Description("number")] + Number, + + [Description("search")] + Search, + + [Description("tel")] + Telephone, + + [Description("url")] + Url, + + [Description("color")] + Color, + + [Description("date")] + Date, + + [Description("datetime-local")] + DateTimeLocal, + + [Description("month")] + Month, + + [Description("time")] + Time, + + [Description("week")] + Week } diff --git a/Utilities/BindingConverters/Converter.cs b/Utilities/BindingConverters/Converter.cs index 2f97eab..ffc4b0f 100644 --- a/Utilities/BindingConverters/Converter.cs +++ b/Utilities/BindingConverters/Converter.cs @@ -4,62 +4,62 @@ namespace Connected; public class Converter { - public event EventHandler? ErrorOccured; - - /// - /// The culture info being used for decimal points, date and time format, etc. - /// - public CultureInfo Culture { get; set; } = Converters.DefaultCulture; - - public TDestinationType? Convert(TSourceType value) - { - try - { - return ConvertValue(value); - } - catch (Exception e) - { - TriggerError($"Conversion from {typeof(TSourceType).Name} to {typeof(TDestinationType).Name} failed: {e.Message}", e); - } - - return default; - } - - protected virtual TDestinationType? ConvertValue(TSourceType? value) - { - return default; - } - - public TSourceType? ConvertBack(TDestinationType value) - { - try - { - return ConvertValueBack(value); - } - catch (Exception e) - { - TriggerError($"Conversion from {typeof(TDestinationType).Name} to {typeof(TSourceType).Name} failed: {e.Message}", e); - } - - return default; - } - - protected virtual TSourceType? ConvertValueBack(TDestinationType? value) - { - return default; - } - - protected void TriggerError(string? msg, Exception? e = null) - { - ErrorOccured?.Invoke(this, msg); - - OnErrorOccured(msg, e); - } - - protected virtual void OnErrorOccured(string? msg, Exception? e) - { - - } + public event EventHandler? ErrorOccured; + + /// + /// The culture info being used for decimal points, date and time format, etc. + /// + public CultureInfo Culture { get; set; } = Converters.DefaultCulture; + + public TDestinationType? Convert(TSourceType value) + { + try + { + return ConvertValue(value); + } + catch (Exception e) + { + TriggerError($"Conversion from {typeof(TSourceType).Name} to {typeof(TDestinationType).Name} failed: {e.Message}", e); + } + + return default; + } + + protected virtual TDestinationType? ConvertValue(TSourceType? value) + { + return default; + } + + public TSourceType? ConvertBack(TDestinationType value) + { + try + { + return ConvertValueBack(value); + } + catch (Exception e) + { + TriggerError($"Conversion from {typeof(TDestinationType).Name} to {typeof(TSourceType).Name} failed: {e.Message}", e); + } + + return default; + } + + protected virtual TSourceType? ConvertValueBack(TDestinationType? value) + { + return default; + } + + protected void TriggerError(string? msg, Exception? e = null) + { + ErrorOccured?.Invoke(this, msg); + + OnErrorOccured(msg, e); + } + + protected virtual void OnErrorOccured(string? msg, Exception? e) + { + + } } /// @@ -67,32 +67,32 @@ public class Converter /// /// The source type /// The destination type -public class LambdaConverter :Converter +public class LambdaConverter : Converter { - private readonly Func? _convertFunction; - private readonly Func? _convertBackFunction; - - public LambdaConverter(Func? convertFunction = null, Func? convertBackFunction = null) - { - _convertFunction = convertFunction; - _convertBackFunction = convertBackFunction; - } - - protected override TDestinationType? ConvertValue(TSourceType? value) - { - if (_convertFunction is null) - return base.ConvertValue(value); - - return _convertFunction.Invoke(value); - } - - protected override TSourceType? ConvertValueBack(TDestinationType? value) - { - if (_convertFunction is null) - return base.ConvertValueBack(value); - - return _convertBackFunction.Invoke(value); - } + private readonly Func? _convertFunction; + private readonly Func? _convertBackFunction; + + public LambdaConverter(Func? convertFunction = null, Func? convertBackFunction = null) + { + _convertFunction = convertFunction; + _convertBackFunction = convertBackFunction; + } + + protected override TDestinationType? ConvertValue(TSourceType? value) + { + if (_convertFunction is null) + return base.ConvertValue(value); + + return _convertFunction.Invoke(value); + } + + protected override TSourceType? ConvertValueBack(TDestinationType? value) + { + if (_convertFunction is null) + return base.ConvertValueBack(value); + + return _convertBackFunction.Invoke(value); + } } /// @@ -104,8 +104,8 @@ public class LambdaConverter :Converter : Converter { - /// - /// Custom Format to be applied on bidirectional way. - /// - public string? Format { get; set; } = null; + /// + /// Custom Format to be applied on bidirectional way. + /// + public string? Format { get; set; } = null; } diff --git a/Utilities/BindingConverters/Converters.cs b/Utilities/BindingConverters/Converters.cs index 3bff428..7e26a00 100644 --- a/Utilities/BindingConverters/Converters.cs +++ b/Utilities/BindingConverters/Converters.cs @@ -1,54 +1,8 @@ -using Connected.Extensions; -using System.Globalization; +using System.Globalization; -namespace Connected -{ - public static class Converters - { - public static CultureInfo DefaultCulture = CultureInfo.CurrentUICulture; - - #region --> Date converters - public static LambdaConverter IsoDate - => new(SetIsoDate, GetIsoDate); - - private static DateTime GetIsoDate(string value) - { - if (DateTime.TryParse(value, out var dateTime)) - return dateTime; - return DateTime.MinValue; - } - - private static string SetIsoDate(DateTime value) - { - return value.ToIsoDateString(); - } - - public static LambdaConverter NullableIsoDate - => new(SetNullableIsoDate, GetNullableIsoDate); +namespace Connected; - private static DateTime? GetNullableIsoDate(string? value) - { - if (DateTime.TryParse(value, out var dateTime)) - return dateTime; - return null; - } - - private static string SetNullableIsoDate(DateTime? value) - { - return value.ToIsoDateString(); - } - - public static DateConverter DateFormat(string format) - { - format ??= "yyyy-MM-dd"; - return new DateConverter(format); - } - - public static DateConverter DateFormat(string format, CultureInfo culture) - { - return new DateConverter(format) { Culture = culture }; - } - - #endregion - } +public static class Converters +{ + public static CultureInfo DefaultCulture { get; set; } = CultureInfo.CurrentUICulture; } diff --git a/Utilities/BindingConverters/DateConverter.cs b/Utilities/BindingConverters/DateConverter.cs index 789c8ab..489ddaf 100644 --- a/Utilities/BindingConverters/DateConverter.cs +++ b/Utilities/BindingConverters/DateConverter.cs @@ -1,106 +1,29 @@ -using System; +namespace Connected; -namespace Connected +/// +/// A ready made DateTime? to string binding converter with configurable date format and culture +/// +public class NullableDateConverter : ToStringConverter { + public string DateFormat { get; set; } - /// - /// A ready made DateTime? to string binding converter with configurable date format and culture - /// - public class NullableDateConverter : ToStringConverter - { - public string DateFormat { get; set; } + public NullableDateConverter(string format = "yyyy-MM-dd") => DateFormat = format; - public NullableDateConverter(string format = "yyyy-MM-dd") - { - DateFormat = format; - } + protected override string? ConvertValue(DateTime? value) => value?.ToString(DateFormat, Culture); - protected override string? ConvertValue(DateTime? value) - { - return OnSet(value); - } - - protected override DateTime? ConvertValueBack(string? value) - { - return OnGet(value); - } - - private DateTime? OnGet(string arg) - { - try - { - return DateTime.ParseExact(arg, DateFormat, Culture); - } - catch (FormatException e) - { - TriggerError(e.Message); - return null; - } - } - - private string OnSet(DateTime? arg) - { - if (arg == null) - return null; - try - { - return arg.Value.ToString(DateFormat, Culture); - } - catch (FormatException e) - { - TriggerError(e.Message); - return null; - } - } - } - - /// - /// A ready made DateTime to string binding converter with configurable date format and culture - /// - public class DateConverter : ToStringConverter - { - public string DateFormat { get; set; } = "yyyy-MM-dd"; - - public DateConverter(string format) - { - DateFormat = format; - } - - protected override string? ConvertValue(DateTime value) - { - return OnSet(value); - } + protected override DateTime? ConvertValueBack(string? value) => DateTime.ParseExact(value, DateFormat, Culture); +} - protected override DateTime ConvertValueBack(string value) - { - return OnGet(value); - } +/// +/// A ready made DateTime to string binding converter with configurable date format and culture +/// +public class DateConverter : ToStringConverter +{ + public string DateFormat { get; set; } - private DateTime OnGet(string arg) - { - try - { - return DateTime.ParseExact(arg, DateFormat, Culture); - } - catch (FormatException e) - { - TriggerError(e.Message); - return default; - } - } + public DateConverter(string format = "yyyy-MM-dd") => DateFormat = format; - private string OnSet(DateTime arg) - { - try - { - return arg.ToString(DateFormat, Culture); - } - catch (FormatException e) - { - TriggerError(e.Message); - return null; - } - } - } + protected override string? ConvertValue(DateTime value) => value.ToString(DateFormat, Culture); -} + protected override DateTime ConvertValueBack(string? value) => DateTime.ParseExact(value, DateFormat, Culture); +} \ No newline at end of file diff --git a/Utilities/BindingConverters/DefaultConverter.cs b/Utilities/BindingConverters/DefaultConverter.cs index 38bd620..7e4b259 100644 --- a/Utilities/BindingConverters/DefaultConverter.cs +++ b/Utilities/BindingConverters/DefaultConverter.cs @@ -1,323 +1,464 @@ -using System; -using System.Globalization; +using System.Globalization; -namespace Connected +namespace Connected; + +/// +/// A universal T to string binding converter +/// +public class DefaultConverter : ToStringConverter { + protected override string? ConvertValue(T? value) => ConvertToString(value); + + protected override T? ConvertValueBack(string? value) => ConvertFromString(value); + + public string DefaultTimeSpanFormat { get; set; } = "c"; + + protected virtual T? ConvertFromString(string? value) + { + try + { + /* + * This is important, or otherwise all the TryParse down there might fail. + */ + if (string.IsNullOrEmpty(value)) + { + return default; + } + /* + * String + */ + else if (typeof(T) == typeof(string)) + { + return (T)(object)value; + } + /* + * Char + */ + else if (typeof(T) == typeof(char) || typeof(T) == typeof(char?)) + { + return (T)(object)value[0]; + } + /* + * Bool + */ + else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) + { + var lowerValue = value.ToLowerInvariant(); + + if (lowerValue is "true" or "on") + return (T)(object)true; + + if (lowerValue is "false" or "off") + return (T)(object)false; + + TriggerError("Not a valid boolean"); + } + /* + * Sbyte + */ + else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?)) + { + if (sbyte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Byte + */ + else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?)) + { + if (byte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Short + */ + else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?)) + { + if (short.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Ushort + */ + else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?)) + { + if (ushort.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Int + */ + else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) + { + if (int.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Uint + */ + else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?)) + { + if (uint.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Long + */ + else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) + { + if (long.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Ulong + */ + else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?)) + { + if (ulong.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Float + */ + else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?)) + { + if (float.TryParse(value, NumberStyles.Any, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Double + */ + else if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) + { + if (double.TryParse(value, NumberStyles.Any, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Decimal + */ + else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) + { + if (decimal.TryParse(value, NumberStyles.Any, Culture, out var parsedValue)) + return (T)(object)parsedValue; + + TriggerError("Not a valid number"); + } + /* + * Guid + */ + else if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?)) + { + if (Guid.TryParse(value, out var parsedValue)) + return (T)(object)parsedValue; - /// - /// A universal T to string binding converter - /// - public class DefaultConverter : ToStringConverter - { + TriggerError("Not a valid GUID"); + } + /* + * Enum + */ + else if (IsNullableEnum(typeof(T))) + { + var enum_type = Nullable.GetUnderlyingType(typeof(T)); - public DefaultConverter() - { - } + if (Enum.TryParse(enum_type, value, out var parsedValue)) + return (T)parsedValue; - protected override string? ConvertValue(T? value) - { - return ConvertToString(value); - } + TriggerError("Not a value of " + enum_type.Name); + } + else if (typeof(T).IsEnum) + { + if (Enum.TryParse(typeof(T), value, out var parsedValue)) + return (T)parsedValue; - protected override T? ConvertValueBack(string? value) - { - return ConvertFromString(value); - } + TriggerError("Not a value of " + typeof(T).Name); + } + /* + * DateTime + */ + else if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?)) + { + try + { + return (T)(object)DateTime.ParseExact(value, Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture); + } + catch (FormatException) + { + TriggerError("Not a valid date time"); + } + } + /* + * Timespan + */ + else if (typeof(T) == typeof(TimeSpan) || typeof(T) == typeof(TimeSpan?)) + { + try + { + return (T)(object)TimeSpan.ParseExact(value, Format ?? DefaultTimeSpanFormat, Culture); + } + catch (FormatException) + { + TriggerError("Not a valid time span"); + } + } + else + { + TriggerError($"Conversion to type {typeof(T)} not implemented"); + } + } + catch (Exception e) + { + TriggerError("Conversion error: " + e.Message); + } - public string DefaultTimeSpanFormat { get; set; } = "c"; + return default; + } - protected virtual T ConvertFromString(string value) - { - try - { - // string - if (typeof(T) == typeof(string)) - return (T)(object)value; + protected virtual string? ConvertToString(T? arg) + { + /* + * This catches all null values. No additional null checks necessary. + */ + if (arg is null) + return null; - // this is important, or otherwise all the TryParse down there might fail. - if (string.IsNullOrEmpty(value)) - return default(T); - // char - else if (typeof(T) == typeof(char) || typeof(T) == typeof(char?)) - { - return (T)(object)value[0]; - } - // bool - else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) - { - var lowerValue = value.ToLowerInvariant(); - if (lowerValue is "true" or "on") - return (T)(object)true; - if (lowerValue is "false" or "off") - return (T)(object)false; - TriggerError("Not a valid boolean"); - } - // sbyte - else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?)) - { - if (sbyte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // byte - else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?)) - { - if (byte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // short - else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?)) - { - if (short.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // ushort - else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?)) - { - if (ushort.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // int - else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) - { - if (int.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // uint - else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?)) - { - if (uint.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // long - else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) - { - if (long.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // ulong - else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?)) - { - if (ulong.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // float - else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?)) - { - if (float.TryParse(value, NumberStyles.Any, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // double - else if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) - { - if (double.TryParse(value, NumberStyles.Any, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // decimal - else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) - { - if (decimal.TryParse(value, NumberStyles.Any, Culture, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid number"); - } - // guid - else if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?)) - { - if (Guid.TryParse(value, out var parsedValue)) - return (T)(object)parsedValue; - TriggerError("Not a valid GUID"); - } - // enum - else if (IsNullableEnum(typeof(T))) - { - var enum_type = Nullable.GetUnderlyingType(typeof(T)); - if (Enum.TryParse(enum_type, value, out var parsedValue)) - return (T)parsedValue; - TriggerError("Not a value of " + enum_type.Name); - } - else if (typeof(T).IsEnum) - { - if (Enum.TryParse(typeof(T), value, out var parsedValue)) - return (T)parsedValue; - TriggerError("Not a value of " + typeof(T).Name); - } - // datetime - else if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?)) - { - try - { - return (T)(object)DateTime.ParseExact(value, Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture); - } - catch (FormatException) - { - TriggerError("Not a valid date time"); - } - } - // timespan - else if (typeof(T) == typeof(TimeSpan) || typeof(T) == typeof(TimeSpan?)) - { - try - { - return (T)(object)TimeSpan.ParseExact(value, Format ?? DefaultTimeSpanFormat, Culture); - } - catch (FormatException) - { - TriggerError("Not a valid time span"); - } - } - else - { - TriggerError($"Conversion to type {typeof(T)} not implemented"); - } - } - catch (Exception e) - { - TriggerError("Conversion error: " + e.Message); - } + try + { + /* + * String + */ + if (typeof(T) == typeof(string)) + { + return (string)(object)arg; + } + /* + * Char + */ + else if (typeof(T) == typeof(char)) + { + return ((char)(object)arg).ToString(Culture); + } + else if (typeof(T) == typeof(char?)) + { + return ((char?)(object)arg).Value.ToString(Culture); + } + /* + * Bool + */ + else if (typeof(T) == typeof(bool)) + { + return ((bool)(object)arg).ToString(CultureInfo.InvariantCulture); + } + else if (typeof(T) == typeof(bool?)) + { + return ((bool?)(object)arg).Value.ToString(CultureInfo.InvariantCulture); + } + /* + * Sbyte + */ + else if (typeof(T) == typeof(sbyte)) + { + return ((sbyte)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(sbyte?)) + { + return ((sbyte?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Byte + */ + else if (typeof(T) == typeof(byte)) + { + return ((byte)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(byte?)) + { + return ((byte?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Short + */ + else if (typeof(T) == typeof(short)) + { + return ((short)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(short?)) + { + return ((short?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Ushort + */ + else if (typeof(T) == typeof(ushort)) + { + return ((ushort)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(ushort?)) + { + return ((ushort?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Int + */ + else if (typeof(T) == typeof(int)) + { + return ((int)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(int?)) + { + return ((int?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Uint + */ + else if (typeof(T) == typeof(uint)) + { + return ((uint)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(uint?)) + { + return ((uint?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Long + */ + else if (typeof(T) == typeof(long)) + { + return ((long)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(long?)) + { + return ((long?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Ulong + */ + else if (typeof(T) == typeof(ulong)) + { + return ((ulong)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(ulong?)) + { + return ((ulong?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Float + */ + else if (typeof(T) == typeof(float)) + { + return ((float)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(float?)) + { + return ((float?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Double + */ + else if (typeof(T) == typeof(double)) + { + return ((double)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(double?)) + { + return ((double?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Decimal + */ + else if (typeof(T) == typeof(decimal)) + { + return ((decimal)(object)arg).ToString(Format, Culture); + } + else if (typeof(T) == typeof(decimal?)) + { + return ((decimal?)(object)arg).Value.ToString(Format, Culture); + } + /* + * Guid + */ + else if (typeof(T) == typeof(Guid)) + { + var value = (Guid)(object)arg; + return value.ToString(); + } + else if (typeof(T) == typeof(Guid?)) + { + var value = (Guid?)(object)arg; + return value.Value.ToString(); + } + /* + * Enum + */ + else if (IsNullableEnum(typeof(T))) + { + var value = (Enum)(object)arg; + return value.ToString(); + } + else if (typeof(T).IsEnum) + { + var value = (Enum)(object)arg; + return value.ToString(); + } + /* + * DateTime + */ + else if (typeof(T) == typeof(DateTime)) + { + var value = (DateTime)(object)arg; + return value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture); + } + else if (typeof(T) == typeof(DateTime?)) + { + var value = (DateTime?)(object)arg; + return value.Value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture); + } + /* + * Timespan + */ + else if (typeof(T) == typeof(TimeSpan)) + { + var value = (TimeSpan)(object)arg; + return value.ToString(Format ?? DefaultTimeSpanFormat, Culture); + } + else if (typeof(T) == typeof(TimeSpan?)) + { + var value = (TimeSpan?)(object)arg; + return value.Value.ToString(Format ?? DefaultTimeSpanFormat, Culture); + } - return default(T); - } + return arg.ToString(); + } + catch (FormatException e) + { + TriggerError("Conversion error: " + e.Message); + return null; + } + } - protected virtual string ConvertToString(T arg) - { - if (arg == null) - return null; // <-- this catches all nullable values which are null. no nullchecks necessary below! - try - { - // string - if (typeof(T) == typeof(string)) - return (string)(object)arg; - // char - if (typeof(T) == typeof(char)) - return ((char)(object)arg).ToString(Culture); - if (typeof(T) == typeof(char?)) - return ((char?)(object)arg).Value.ToString(Culture); - // bool - if (typeof(T) == typeof(bool)) - return ((bool)(object)arg).ToString(CultureInfo.InvariantCulture); - if (typeof(T) == typeof(bool?)) - return ((bool?)(object)arg).Value.ToString(CultureInfo.InvariantCulture); - // sbyte - if (typeof(T) == typeof(sbyte)) - return ((sbyte)(object)arg).ToString(Format, Culture); - if (typeof(T) == typeof(sbyte?)) - return ((sbyte?)(object)arg).Value.ToString(Format, Culture); - // byte - if (typeof(T) == typeof(byte)) - return ((byte)(object)arg).ToString(Format, Culture); - if (typeof(T) == typeof(byte?)) - return ((byte?)(object)arg).Value.ToString(Format, Culture); - // short - if (typeof(T) == typeof(short)) - return ((short)(object)arg).ToString(Format, Culture); - if (typeof(T) == typeof(short?)) - return ((short?)(object)arg).Value.ToString(Format, Culture); - // ushort - if (typeof(T) == typeof(ushort)) - return ((ushort)(object)arg).ToString(Format, Culture); - if (typeof(T) == typeof(ushort?)) - return ((ushort?)(object)arg).Value.ToString(Format, Culture); - // int - else if (typeof(T) == typeof(int)) - return ((int)(object)arg).ToString(Format, Culture); - else if (typeof(T) == typeof(int?)) - return ((int?)(object)arg).Value.ToString(Format, Culture); - // uint - else if (typeof(T) == typeof(uint)) - return ((uint)(object)arg).ToString(Format, Culture); - else if (typeof(T) == typeof(uint?)) - return ((uint?)(object)arg).Value.ToString(Format, Culture); - // long - else if (typeof(T) == typeof(long)) - return ((long)(object)arg).ToString(Format, Culture); - else if (typeof(T) == typeof(long?)) - return ((long?)(object)arg).Value.ToString(Format, Culture); - // ulong - else if (typeof(T) == typeof(ulong)) - return ((ulong)(object)arg).ToString(Format, Culture); - else if (typeof(T) == typeof(ulong?)) - return ((ulong?)(object)arg).Value.ToString(Format, Culture); - // float - else if (typeof(T) == typeof(float)) - return ((float)(object)arg).ToString(Format, Culture); - else if (typeof(T) == typeof(float?)) - return ((float?)(object)arg).Value.ToString(Format, Culture); - // double - else if (typeof(T) == typeof(double)) - return ((double)(object)arg).ToString(Format, Culture); - else if (typeof(T) == typeof(double?)) - return ((double?)(object)arg).Value.ToString(Format, Culture); - // decimal - else if (typeof(T) == typeof(decimal)) - return ((decimal)(object)arg).ToString(Format, Culture); - else if (typeof(T) == typeof(decimal?)) - return ((decimal?)(object)arg).Value.ToString(Format, Culture); - // guid - else if (typeof(T) == typeof(Guid)) - { - var value = (Guid)(object)arg; - return value.ToString(); - } - else if (typeof(T) == typeof(Guid?)) - { - var value = (Guid?)(object)arg; - return value.Value.ToString(); - } - // enum - else if (IsNullableEnum(typeof(T))) - { - var value = (Enum)(object)arg; - return value.ToString(); - } - else if (typeof(T).IsEnum) - { - var value = (Enum)(object)arg; - return value.ToString(); - } - // datetime - else if (typeof(T) == typeof(DateTime)) - { - var value = (DateTime)(object)arg; - return value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture); - } - else if (typeof(T) == typeof(DateTime?)) - { - var value = (DateTime?)(object)arg; - return value.Value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture); - } - // timespan - else if (typeof(T) == typeof(TimeSpan)) - { - var value = (TimeSpan)(object)arg; - return value.ToString(Format ?? DefaultTimeSpanFormat, Culture); - } - else if (typeof(T) == typeof(TimeSpan?)) - { - var value = (TimeSpan?)(object)arg; - return value.Value.ToString(Format ?? DefaultTimeSpanFormat, Culture); - } - return arg.ToString(); - } - catch (FormatException e) - { - TriggerError("Conversion error: " + e.Message); - return null; - } - } + public static bool IsNullableEnum(Type t) + { + var u = Nullable.GetUnderlyingType(t); - public static bool IsNullableEnum(Type t) - { - var u = Nullable.GetUnderlyingType(t); - return (u != null) && u.IsEnum; - } - } + return (u is not null) && u.IsEnum; + } } diff --git a/Utilities/BindingConverters/IsoDateConverter.cs b/Utilities/BindingConverters/IsoDateConverter.cs new file mode 100644 index 0000000..6d67662 --- /dev/null +++ b/Utilities/BindingConverters/IsoDateConverter.cs @@ -0,0 +1,17 @@ +using Connected.Extensions; + +namespace Connected; + +public class IsoDateConverter : Converter +{ + protected override string ConvertValue(DateTime value) => value.ToIsoDateString(); + + protected override DateTime ConvertValueBack(string? value) => DateTime.TryParse(value, out var dateTime) ? dateTime : DateTime.MinValue; +} + +public class NullableIsoDateConverter : Converter +{ + protected override string? ConvertValue(DateTime? value) => value?.ToIsoDateString(); + + protected override DateTime? ConvertValueBack(string? value) => DateTime.TryParse(value, out var dateTime) ? dateTime : null; +} diff --git a/Utilities/BindingConverters/NumericConverter.cs b/Utilities/BindingConverters/NumericConverter.cs index bc6cafb..8f79840 100644 --- a/Utilities/BindingConverters/NumericConverter.cs +++ b/Utilities/BindingConverters/NumericConverter.cs @@ -1,293 +1,390 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Globalization; -namespace Connected -{ - - /// - /// A universal T to double binding converter - /// - /// Note: currently not in use. Should we ever use it, remove - /// the [ExcludeFromCodeCoverage] attribute - /// - [ExcludeFromCodeCoverage] - public class NumericConverter : Converter - { +namespace Connected; - public NumericConverter() - { - } +/// +/// A universal T to double binding converter +/// +/// Note: currently not in use. Should we ever use it, remove +/// the [ExcludeFromCodeCoverage] attribute +/// +[ExcludeFromCodeCoverage] +public class NumericConverter : Converter +{ - protected override double ConvertValue(T? value) + protected override double ConvertValue(T? value) + { + if (value is null) { - return OnSet(value); + return double.NaN; } - - protected override T? ConvertValueBack(double value) + /* + * Double + */ + else if (typeof(T) == typeof(double)) { - return OnGet(value); + return (double)(object)value; } + else if (typeof(T) == typeof(double?)) + { + return ((double?)(object)value).Value; + } + /* + * String + */ + else if (typeof(T) == typeof(string)) + { + return double.Parse((string)(object)value, NumberStyles.Any, Culture); + } + /* + * SByte + */ + else if (typeof(T) == typeof(sbyte)) + { + return System.Convert.ToDouble((sbyte)(object)value); + } + else if (typeof(T) == typeof(sbyte?)) + { + return System.Convert.ToDouble(((sbyte?)(object)value).Value); + } + /* + * Byte + */ + else if (typeof(T) == typeof(byte)) + { + return System.Convert.ToDouble((byte)(object)value); + } + else if (typeof(T) == typeof(byte?)) + { + return System.Convert.ToDouble(((byte?)(object)value).Value); + } + /* + * Short + */ + else if (typeof(T) == typeof(short)) + { + return System.Convert.ToDouble((short)(object)value); + } + else if (typeof(T) == typeof(short?)) + { + return System.Convert.ToDouble(((short?)(object)value).Value); + } + /* + * Ushort + */ + else if (typeof(T) == typeof(ushort)) + { + return System.Convert.ToDouble((ushort)(object)value); + } + else if (typeof(T) == typeof(ushort?)) + { + return System.Convert.ToDouble(((ushort?)(object)value).Value); + } + /* + * Int + */ + else if (typeof(T) == typeof(int)) + { + return System.Convert.ToDouble((int)(object)value); + } + else if (typeof(T) == typeof(int?)) + { + return System.Convert.ToDouble(((int?)(object)value).Value); + } + /* + * Uint + */ + else if (typeof(T) == typeof(uint)) + { + return System.Convert.ToDouble((uint)(object)value); + } + else if (typeof(T) == typeof(uint?)) + { + return System.Convert.ToDouble(((uint?)(object)value).Value); + } + /* + * Long + */ + else if (typeof(T) == typeof(long)) + { + return System.Convert.ToDouble((long)(object)value); + } + else if (typeof(T) == typeof(long?)) + { + return System.Convert.ToDouble(((long?)(object)value).Value); + } + /* + * Ulong + */ + else if (typeof(T) == typeof(ulong)) + { + return System.Convert.ToDouble((ulong)(object)value); + } + else if (typeof(T) == typeof(ulong?)) + { + return System.Convert.ToDouble(((ulong?)(object)value).Value); + } + /* + * Float + */ + else if (typeof(T) == typeof(float)) + { + return System.Convert.ToDouble((float)(object)value); + } + else if (typeof(T) == typeof(float?)) + { + return System.Convert.ToDouble(((float?)(object)value).Value); + } + /* + * Deimal + */ + else if (typeof(T) == typeof(decimal)) + { + return System.Convert.ToDouble((decimal)(object)value); + } + else if (typeof(T) == typeof(decimal?)) + { + return System.Convert.ToDouble(((decimal?)(object)value).Value); + } + else + { + TriggerError("Unable to convert to double from type " + typeof(T).Name); + return double.NaN; + } + } - private T OnGet(double value) - { - try - { - // double - if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) - return (T)(object)value; - // string - else if (typeof(T) == typeof(string)) - return (T)(object)value.ToString(Culture); - // sbyte - else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?)) - return (T)(object)System.Convert.ToSByte(value); - // byte - else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?)) - return (T)(object)System.Convert.ToByte(value); - // short - else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?)) - return (T)(object)System.Convert.ToInt16(value); - // ushort - else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?)) - return (T)(object)System.Convert.ToUInt16(value); - // int - else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) - return (T)(object)System.Convert.ToInt32(value); - // uint - else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?)) - return (T)(object)System.Convert.ToUInt32(value); - // long - else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) - return (T)(object)System.Convert.ToInt64(value); - // ulong - else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?)) - return (T)(object)System.Convert.ToUInt64(value); - // float - else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?)) - return (T)(object)System.Convert.ToSingle(value); - // decimal - else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) - return (T)(object)System.Convert.ToDecimal(value); - else - { - TriggerError($"Conversion to type {typeof(T)} not implemented"); - } - } - catch (Exception e) - { - TriggerError("Conversion error: " + e.Message); - return default(T); - } - return default(T); - } - - private double OnSet(T arg) - { - if (arg == null) - return double.NaN; // <-- this catches all nullable values which are null. no nullchecks necessary below! - try - { - // double - if (typeof(T) == typeof(double)) - return (double)(object)arg; - else if (typeof(T) == typeof(double?)) - return ((double?)(object)arg).Value; - // string - if (typeof(T) == typeof(string)) - return double.Parse((string)(object)arg, NumberStyles.Any, Culture); - // sbyte - if (typeof(T) == typeof(sbyte)) - return System.Convert.ToDouble((sbyte)(object)arg); - if (typeof(T) == typeof(sbyte?)) - return System.Convert.ToDouble(((sbyte?)(object)arg).Value); - // byte - if (typeof(T) == typeof(byte)) - return System.Convert.ToDouble((byte)(object)arg); - if (typeof(T) == typeof(byte?)) - return System.Convert.ToDouble(((byte?)(object)arg).Value); - // short - if (typeof(T) == typeof(short)) - return System.Convert.ToDouble((short)(object)arg); - if (typeof(T) == typeof(short?)) - return System.Convert.ToDouble(((short?)(object)arg).Value); - // ushort - if (typeof(T) == typeof(ushort)) - return System.Convert.ToDouble((ushort)(object)arg); - if (typeof(T) == typeof(ushort?)) - return System.Convert.ToDouble(((ushort?)(object)arg).Value); - // int - else if (typeof(T) == typeof(int)) - return System.Convert.ToDouble((int)(object)arg); - else if (typeof(T) == typeof(int?)) - return System.Convert.ToDouble(((int?)(object)arg).Value); - // uint - else if (typeof(T) == typeof(uint)) - return System.Convert.ToDouble((uint)(object)arg); - else if (typeof(T) == typeof(uint?)) - return System.Convert.ToDouble(((uint?)(object)arg).Value); - // long - else if (typeof(T) == typeof(long)) - return System.Convert.ToDouble((long)(object)arg); - else if (typeof(T) == typeof(long?)) - return System.Convert.ToDouble(((long?)(object)arg).Value); - // ulong - else if (typeof(T) == typeof(ulong)) - return System.Convert.ToDouble((ulong)(object)arg); - else if (typeof(T) == typeof(ulong?)) - return System.Convert.ToDouble(((ulong?)(object)arg).Value); - // float - else if (typeof(T) == typeof(float)) - return System.Convert.ToDouble((float)(object)arg); - else if (typeof(T) == typeof(float?)) - return System.Convert.ToDouble(((float?)(object)arg).Value); - // decimal - else if (typeof(T) == typeof(decimal)) - return System.Convert.ToDouble((decimal)(object)arg); - else if (typeof(T) == typeof(decimal?)) - return System.Convert.ToDouble(((decimal?)(object)arg).Value); - else - { - TriggerError("Unable to convert to double from type " + typeof(T).Name); - return double.NaN; - } - } - catch (FormatException e) - { - TriggerError("Conversion error: " + e.Message); - return double.NaN; - } - } + protected override T? ConvertValueBack(double value) + { + /* + * Double + */ + if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) + { + return (T)(object)value; + } + /* + * String + */ + else if (typeof(T) == typeof(string)) + { + return (T)(object)value.ToString(Culture); + } + /* + * Sbyte + */ + else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?)) + { + return (T)(object)System.Convert.ToSByte(value); + } + /* + * Byte + */ + else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?)) + { + return (T)(object)System.Convert.ToByte(value); + } + /* + * Short + */ + else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?)) + { + return (T)(object)System.Convert.ToInt16(value); + } + /* + * Ushort + */ + else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?)) + { + return (T)(object)System.Convert.ToUInt16(value); + } + /* + * Int + */ + else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) + { + return (T)(object)System.Convert.ToInt32(value); + } + /* + * Uint + */ + else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?)) + { + return (T)(object)System.Convert.ToUInt32(value); + } + /* + * Long + */ + else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) + { + return (T)(object)System.Convert.ToInt64(value); + } + /* + * Ulong + */ + else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?)) + { + return (T)(object)System.Convert.ToUInt64(value); + } + /* + * Float + */ + else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?)) + { + return (T)(object)System.Convert.ToSingle(value); + } + /* + * Decimal + */ + else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) + { + return (T)(object)System.Convert.ToDecimal(value); + } + else + { + TriggerError($"Conversion to type {typeof(T)} not implemented"); + return default; + } + } + #region Floating Point comparison - #region --> Floating Point comparison + private const double MinNormal = 2.2250738585072014E-308d; - const double MinNormal = 2.2250738585072014E-308d; + public static bool AreEqual(double a, double b, double epsilon = MinNormal) + { + /* + * Copyright (c) Michael Borgwardt + */ + var absA = Math.Abs(a); + var absB = Math.Abs(b); + var diff = Math.Abs(a - b); + /* + * Shortcut, handles infinities + */ + if (a.Equals(b)) + { + return true; + } + else if (a == 0 || b == 0 || absA + absB < MinNormal) + { + /* + * a or b is zero or both are extremely close to it + * relative error is less meaningful here + */ + return diff < epsilon * MinNormal; + } + else + { + /* + * Use relative error + */ + return diff / (absA + absB) < epsilon; + } + } - public static bool AreEqual(double a, double b, double epsilon = MinNormal) - { - // Copyright (c) Michael Borgwardt - var absA = Math.Abs(a); - var absB = Math.Abs(b); - var diff = Math.Abs(a - b); + #endregion +} - if (a.Equals(b)) - { // shortcut, handles infinities - return true; - } - else if (a == 0 || b == 0 || absA + absB < MinNormal) - { - // a or b is zero or both are extremely close to it - // relative error is less meaningful here - return diff < (epsilon * MinNormal); - } - else - { // use relative error - return diff / (absA + absB) < epsilon; - } - } +[ExcludeFromCodeCoverage] +internal static class Number +{ + public static T? To(double d) + { + if (typeof(T) == typeof(sbyte) && d >= sbyte.MinValue && sbyte.MaxValue >= d) + return (T)(object)Convert.ToSByte(d); + if (typeof(T) == typeof(byte) && d >= byte.MinValue && byte.MaxValue >= d) + return (T)(object)Convert.ToByte(d); + if (typeof(T) == typeof(short) && d >= short.MinValue && short.MaxValue >= d) + return (T)(object)Convert.ToInt16(d); + if (typeof(T) == typeof(ushort) && d >= ushort.MinValue && ushort.MaxValue >= d) + return (T)(object)Convert.ToUInt16(d); + if (typeof(T) == typeof(int) && d >= int.MinValue && int.MaxValue >= d) + return (T)(object)Convert.ToInt32(d); + if (typeof(T) == typeof(uint) && d >= uint.MinValue && uint.MaxValue >= d) + return (T)(object)Convert.ToUInt32(d); + if (typeof(T) == typeof(long) && d >= long.MinValue && long.MaxValue >= d) + return (T)(object)Convert.ToInt64(d); + if (typeof(T) == typeof(ulong) && d >= ulong.MinValue && ulong.MaxValue >= d) + return (T)(object)Convert.ToUInt64(d); + if (typeof(T) == typeof(float) && d >= float.MinValue && float.MaxValue >= d) + return (T)(object)Convert.ToSingle(d); + if (typeof(T) == typeof(double) && d >= double.MinValue && double.MaxValue >= d) + return (T)(object)Convert.ToDouble(d); + if (typeof(T) == typeof(decimal) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d) + return (T)(object)Convert.ToDecimal(d); + if (typeof(T) == typeof(sbyte?) && d >= sbyte.MinValue && sbyte.MaxValue >= d) + return (T)(object)Convert.ToSByte(d); + if (typeof(T) == typeof(byte?) && d >= byte.MinValue && byte.MaxValue >= d) + return (T)(object)Convert.ToByte(d); + if (typeof(T) == typeof(short?) && d >= short.MinValue && short.MaxValue >= d) + return (T)(object)Convert.ToInt16(d); + if (typeof(T) == typeof(ushort?) && d >= ushort.MinValue && ushort.MaxValue >= d) + return (T)(object)Convert.ToUInt16(d); + if (typeof(T) == typeof(int?) && d >= int.MinValue && int.MaxValue >= d) + return (T)(object)Convert.ToInt32(d); + if (typeof(T) == typeof(uint?) && d >= uint.MinValue && uint.MaxValue >= d) + return (T)(object)Convert.ToUInt32(d); + if (typeof(T) == typeof(long?) && d >= long.MinValue && long.MaxValue >= d) + return (T)(object)Convert.ToInt64(d); + if (typeof(T) == typeof(ulong?) && d >= ulong.MinValue && ulong.MaxValue >= d) + return (T)(object)Convert.ToUInt64(d); + if (typeof(T) == typeof(float?) && d >= float.MinValue && float.MaxValue >= d) + return (T)(object)Convert.ToSingle(d); + if (typeof(T) == typeof(double?) && d >= double.MinValue && double.MaxValue >= d) + return (T)(object)Convert.ToDouble(d); + if (typeof(T) == typeof(decimal?) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d) + return (T)(object)Convert.ToDecimal(d); - #endregion - } + return default; + } + public static double From(T v) + { + if (typeof(T) == typeof(sbyte)) + return Convert.ToDouble((sbyte)(object)v); + if (typeof(T) == typeof(byte)) + return Convert.ToDouble((byte)(object)v); + if (typeof(T) == typeof(short)) + return Convert.ToDouble((short)(object)v); + if (typeof(T) == typeof(ushort)) + return Convert.ToDouble((ushort)(object)v); + if (typeof(T) == typeof(int)) + return Convert.ToDouble((int)(object)v); + if (typeof(T) == typeof(uint)) + return Convert.ToDouble((uint)(object)v); + if (typeof(T) == typeof(long)) + return Convert.ToDouble((long)(object)v); + if (typeof(T) == typeof(ulong)) + return Convert.ToDouble((ulong)(object)v); + if (typeof(T) == typeof(float)) + return Convert.ToDouble((float)(object)v); + if (typeof(T) == typeof(double)) + return Convert.ToDouble((double)(object)v); + if (typeof(T) == typeof(decimal)) + return Convert.ToDouble((decimal)(object)v); + if (typeof(T) == typeof(sbyte?)) + return Convert.ToDouble((sbyte?)(object)v); + if (typeof(T) == typeof(byte?)) + return Convert.ToDouble((byte?)(object)v); + if (typeof(T) == typeof(short?)) + return Convert.ToDouble((short?)(object)v); + if (typeof(T) == typeof(ushort?)) + return Convert.ToDouble((ushort?)(object)v); + if (typeof(T) == typeof(int?)) + return Convert.ToDouble((int?)(object)v); + if (typeof(T) == typeof(uint?)) + return Convert.ToDouble((uint?)(object)v); + if (typeof(T) == typeof(long?)) + return Convert.ToDouble((long?)(object)v); + if (typeof(T) == typeof(ulong?)) + return Convert.ToDouble((ulong?)(object)v); + if (typeof(T) == typeof(float?)) + return Convert.ToDouble((float?)(object)v); + if (typeof(T) == typeof(double?)) + return Convert.ToDouble((double?)(object)v); + if (typeof(T) == typeof(decimal?)) + return Convert.ToDouble((decimal?)(object)v); - [ExcludeFromCodeCoverage] - internal static class Num - { - public static T To(double d) - { - if (typeof(T) == typeof(sbyte) && d >= sbyte.MinValue && sbyte.MaxValue >= d) - return (T)(object)Convert.ToSByte(d); - if (typeof(T) == typeof(byte) && d >= byte.MinValue && byte.MaxValue >= d) - return (T)(object)Convert.ToByte(d); - if (typeof(T) == typeof(short) && d >= short.MinValue && short.MaxValue >= d) - return (T)(object)Convert.ToInt16(d); - if (typeof(T) == typeof(ushort) && d >= ushort.MinValue && ushort.MaxValue >= d) - return (T)(object)Convert.ToUInt16(d); - if (typeof(T) == typeof(int) && d >= int.MinValue && int.MaxValue >= d) - return (T)(object)Convert.ToInt32(d); - if (typeof(T) == typeof(uint) && d >= uint.MinValue && uint.MaxValue >= d) - return (T)(object)Convert.ToUInt32(d); - if (typeof(T) == typeof(long) && d >= long.MinValue && long.MaxValue >= d) - return (T)(object)Convert.ToInt64(d); - if (typeof(T) == typeof(ulong) && d >= ulong.MinValue && ulong.MaxValue >= d) - return (T)(object)Convert.ToUInt64(d); - if (typeof(T) == typeof(float) && d >= float.MinValue && float.MaxValue >= d) - return (T)(object)Convert.ToSingle(d); - if (typeof(T) == typeof(double) && d >= double.MinValue && double.MaxValue >= d) - return (T)(object)Convert.ToDouble(d); - if (typeof(T) == typeof(decimal) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d) - return (T)(object)Convert.ToDecimal(d); - if (typeof(T) == typeof(sbyte?) && d >= sbyte.MinValue && sbyte.MaxValue >= d) - return (T)(object)Convert.ToSByte(d); - if (typeof(T) == typeof(byte?) && d >= byte.MinValue && byte.MaxValue >= d) - return (T)(object)Convert.ToByte(d); - if (typeof(T) == typeof(short?) && d >= short.MinValue && short.MaxValue >= d) - return (T)(object)Convert.ToInt16(d); - if (typeof(T) == typeof(ushort?) && d >= ushort.MinValue && ushort.MaxValue >= d) - return (T)(object)Convert.ToUInt16(d); - if (typeof(T) == typeof(int?) && d >= int.MinValue && int.MaxValue >= d) - return (T)(object)Convert.ToInt32(d); - if (typeof(T) == typeof(uint?) && d >= uint.MinValue && uint.MaxValue >= d) - return (T)(object)Convert.ToUInt32(d); - if (typeof(T) == typeof(long?) && d >= long.MinValue && long.MaxValue >= d) - return (T)(object)Convert.ToInt64(d); - if (typeof(T) == typeof(ulong?) && d >= ulong.MinValue && ulong.MaxValue >= d) - return (T)(object)Convert.ToUInt64(d); - if (typeof(T) == typeof(float?) && d >= float.MinValue && float.MaxValue >= d) - return (T)(object)Convert.ToSingle(d); - if (typeof(T) == typeof(double?) && d >= double.MinValue && double.MaxValue >= d) - return (T)(object)Convert.ToDouble(d); - if (typeof(T) == typeof(decimal?) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d) - return (T)(object)Convert.ToDecimal(d); - return default; - } - public static double From(T v) - { - if (typeof(T) == typeof(sbyte)) - return Convert.ToDouble((sbyte)(object)v); - if (typeof(T) == typeof(byte)) - return Convert.ToDouble((byte)(object)v); - if (typeof(T) == typeof(short)) - return Convert.ToDouble((short)(object)v); - if (typeof(T) == typeof(ushort)) - return Convert.ToDouble((ushort)(object)v); - if (typeof(T) == typeof(int)) - return Convert.ToDouble((int)(object)v); - if (typeof(T) == typeof(uint)) - return Convert.ToDouble((uint)(object)v); - if (typeof(T) == typeof(long)) - return Convert.ToDouble((long)(object)v); - if (typeof(T) == typeof(ulong)) - return Convert.ToDouble((ulong)(object)v); - if (typeof(T) == typeof(float)) - return Convert.ToDouble((float)(object)v); - if (typeof(T) == typeof(double)) - return Convert.ToDouble((double)(object)v); - if (typeof(T) == typeof(decimal)) - return Convert.ToDouble((decimal)(object)v); - if (typeof(T) == typeof(sbyte?)) - return Convert.ToDouble((sbyte?)(object)v); - if (typeof(T) == typeof(byte?)) - return Convert.ToDouble((byte?)(object)v); - if (typeof(T) == typeof(short?)) - return Convert.ToDouble((short?)(object)v); - if (typeof(T) == typeof(ushort?)) - return Convert.ToDouble((ushort?)(object)v); - if (typeof(T) == typeof(int?)) - return Convert.ToDouble((int?)(object)v); - if (typeof(T) == typeof(uint?)) - return Convert.ToDouble((uint?)(object)v); - if (typeof(T) == typeof(long?)) - return Convert.ToDouble((long?)(object)v); - if (typeof(T) == typeof(ulong?)) - return Convert.ToDouble((ulong?)(object)v); - if (typeof(T) == typeof(float?)) - return Convert.ToDouble((float?)(object)v); - if (typeof(T) == typeof(double?)) - return Convert.ToDouble((double?)(object)v); - if (typeof(T) == typeof(decimal?)) - return Convert.ToDouble((decimal?)(object)v); - return default; - } - } + return default; + } } diff --git a/Utilities/BindingConverters/RangeConverter.cs b/Utilities/BindingConverters/RangeConverter.cs index a0aa6a5..05a799e 100644 --- a/Utilities/BindingConverters/RangeConverter.cs +++ b/Utilities/BindingConverters/RangeConverter.cs @@ -1,65 +1,37 @@ using Connected.Components; -namespace Connected; +namespace Connected.Utilities.BindingConverters; public class RangeConverter : ToStringConverter> { - readonly DefaultConverter _converter; + private readonly DefaultConverter _converter; - public RangeConverter() - { - _converter = new DefaultConverter(); - } + public RangeConverter() => _converter = new DefaultConverter(); - protected override string? ConvertValue(Range? value) - { - return OnSet(value); - } + protected override string? ConvertValue(Range? value) => value is not null + ? Join(_converter.Convert(value.Start), _converter.Convert(value.End)) + : string.Empty; protected override Range? ConvertValueBack(string? value) { - return OnGet(value); - } - - private Range OnGet(string value) - { - if (!Split(value, out var valueStart, out var valueEnd)) - return null; - - return new Range(_converter.ConvertBack(valueStart), _converter.ConvertBack(valueEnd)); + return !Split(value, out var valueStart, out var valueEnd) + ? null + : new Range(_converter.ConvertBack(valueStart), _converter.ConvertBack(valueEnd)); } - private string OnSet(Range arg) - { - if (arg == null) - return string.Empty; - - return Join(_converter.Convert(arg.Start), _converter.Convert(arg.End)); - } - - public static string Join(string valueStart, string valueEnd) - { - if (string.IsNullOrEmpty(valueStart) && string.IsNullOrEmpty(valueEnd)) - return string.Empty; - - return $"[{valueStart};{valueEnd}]"; - } + public static string Join(string? valueStart, string? valueEnd) => string.IsNullOrEmpty(valueStart) && string.IsNullOrEmpty(valueEnd) ? string.Empty : $"[{valueStart};{valueEnd}]"; - public static bool Split(string value, out string valueStart, out string valueEnd) + public static bool Split(string? value, out string? valueStart, out string? valueEnd) { valueStart = valueEnd = string.Empty; if (string.IsNullOrEmpty(value) || value[0] != '[' || value[^1] != ']') - { return false; - } var idx = value.IndexOf(';'); if (idx < 1) - { return false; - } valueStart = value[1..idx]; valueEnd = value[(idx + 1)..^1];