diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a15cc6b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,347 @@ +[*] +indent_style = space +indent_size = 2 + +[*.cs] +indent_style = tab +indent_size = 4 +insert_final_newline = true + +# Organize usings +dotnet_sort_system_directives_first = true +dotnet_style_namespace_match_folder = true:error +csharp_using_directive_placement = outside_namespace:error +csharp_style_namespace_declarations = block_scoped:error + +# this. preferences +dotnet_style_qualification_for_field = false:error +dotnet_style_qualification_for_property = false:error +dotnet_style_qualification_for_method = false:error +dotnet_style_qualification_for_event = false:error + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:none +dotnet_style_predefined_type_for_member_access = true:none + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:error +dotnet_style_readonly_field = true:warning + +# Expression-level preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:error +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_inferred_tuple_names = false:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = false:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_compound_assignment = true:warning +dotnet_style_prefer_simplified_interpolation = true:suggestion +csharp_prefer_braces = when_multiline:warning +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_prefer_simple_using_statement = true:warning +csharp_style_prefer_method_group_conversion = true:suggestion +csharp_style_prefer_top_level_statements = true:suggestion + +# var preferences +csharp_style_var_for_built_in_types = false:error +csharp_style_var_when_type_is_apparent = false:error +csharp_style_var_elsewhere = false:error + +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Wrapping preferences +dotnet_style_operator_placement_when_wrapping = beginning_of_line +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_constructors = false:warning +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:suggestion + +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:error + +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:warning + +# New line preferences +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false + +############################### +# Naming Conventions # +############################### + +# PascalCase +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# camelCase +dotnet_naming_style.camel_case_style.capitalization = camel_case +# PascalCase + "Async" +dotnet_naming_style.pascal_case_async_style.capitalization = pascal_case +dotnet_naming_style.pascal_case_async_style.required_suffix = Async +# "_" + camelCase +dotnet_naming_style.underscore_camel_case_style.capitalization = camel_case +dotnet_naming_style.underscore_camel_case_style.required_prefix = _ +# Methods and Properties must be pascal case +dotnet_naming_rule.methods_and_properties_must_be_pascal_case.severity = error +dotnet_naming_rule.methods_and_properties_must_be_pascal_case.symbols = method_and_property_symbols +dotnet_naming_rule.methods_and_properties_must_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.method_and_property_symbols.applicable_kinds = method, property +dotnet_naming_symbols.method_and_property_symbols.applicable_accessibilities = * +# Async methods must end in Async +dotnet_naming_rule.async_methods_must_end_with_async.severity = error +dotnet_naming_rule.async_methods_must_end_with_async.symbols = method_symbols +dotnet_naming_rule.async_methods_must_end_with_async.style = pascal_case_async_style +dotnet_naming_symbols.method_symbols.applicable_kinds = method +dotnet_naming_symbols.method_symbols.required_modifiers = async +# Public members must be pascal case +dotnet_naming_rule.public_members_must_be_capitalized.severity = error +dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols +dotnet_naming_rule.public_members_must_be_capitalized.style = pascal_case_style +dotnet_naming_symbols.public_symbols.applicable_kinds = property, method, field, event, delegate +dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal +# Named tuples must be pascal case +dotnet_naming_rule.named_tuples_must_be_capitalized.severity = error +dotnet_naming_rule.named_tuples_must_be_capitalized.symbols = named_tuples +dotnet_naming_rule.named_tuples_must_be_capitalized.style = pascal_case_style +dotnet_naming_symbols.named_tuples.applicable_kinds = tuples +dotnet_naming_symbols.named_tuples.applicable_accessibilities = public, internal, protected, protected_internal +# Fields must be camel case prefixed with an underscore +dotnet_naming_rule.non_public_members_must_be_underscored_camel_case.severity = warning +dotnet_naming_rule.non_public_members_must_be_underscored_camel_case.symbols = fields +dotnet_naming_rule.non_public_members_must_be_underscored_camel_case.style = underscore_camel_case_style +dotnet_naming_symbols.fields.applicable_kinds = field +dotnet_naming_symbols.fields.applicable_accessibilities = private +# Constants must be pascal case +dotnet_naming_rule.constant_fields_should_be_upper_case.severity = warning +dotnet_naming_rule.constant_fields_should_be_upper_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_upper_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +# Static readonly fields must be pascal case +dotnet_naming_rule.static_readonly_fields_should_be_upper_case.severity = warning +dotnet_naming_rule.static_readonly_fields_should_be_upper_case.symbols = static_readonly_fields +dotnet_naming_rule.static_readonly_fields_should_be_upper_case.style = pascal_case_style +dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly +# Locals must be camel case +dotnet_naming_rule.locals_should_be_upper_case.severity = warning +dotnet_naming_rule.locals_should_be_upper_case.symbols = locals +dotnet_naming_rule.locals_should_be_upper_case.style = camel_case_style +dotnet_naming_symbols.locals.applicable_kinds = locals + +############################### +# Code Analyzer Rules # +############################### + +# CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1591.severity = suggestion + +# CA2000: Dispose objects before losing scope +dotnet_diagnostic.CA2000.severity = error + +# IDE0005: Using directive is unnecessary. +dotnet_diagnostic.IDE0005.severity = error + +# IDE0055: Fix formatting +dotnet_diagnostic.IDE0055.severity = suggestion + +# IDE0059: Unnecessary assignment of a value +dotnet_diagnostic.IDE0059.severity = warning + +# IDE0003: Remove qualification +dotnet_diagnostic.IDE0003.severity = warning + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = none + +# RCS1036: Remove unnecessary blank line. +dotnet_diagnostic.RCS1036.severity = error + +# CA1852: Type can be sealed because it has no subtypes +dotnet_diagnostic.CA1852.severity = error + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.CA1305.severity = error + +# CA1001: Types that own disposable fields should be disposable +dotnet_diagnostic.CA1001.severity = error + +# IDE0090: Use 'new(...)' +dotnet_diagnostic.IDE0090.severity = warning + +# IDE0250: Make struct 'readonly' +dotnet_diagnostic.IDE0250.severity = warning + +# RCS1118: Mark local variable as const. +dotnet_diagnostic.RCS1118.severity = warning + +# RCS1214: Unnecessary interpolated string. +dotnet_diagnostic.RCS1214.severity = error + +# RCS1205: Order named arguments according to the order of parameters. +dotnet_diagnostic.RCS1205.severity = error + +# RCS1215: Expression is always equal to true/false. +dotnet_diagnostic.RCS1215.severity = error + +# RCS1043: Remove 'partial' modifier from type with a single part. +dotnet_diagnostic.RCS1043.severity = warning + +# RCS1228: Unused element in documentation comment. +dotnet_diagnostic.RCS1228.severity = warning + +# RCS1196: Call extension method as instance method. +dotnet_diagnostic.RCS1196.severity = warning + +# IDE0017: Simplify object initialization +dotnet_diagnostic.IDE0017.severity = warning + +# RCS1169: Make field read-only. +dotnet_diagnostic.RCS1169.severity = warning + +# RCS1084: Use coalesce expression instead of conditional expression. +dotnet_diagnostic.RCS1084.severity = warning + +# IDE0036: Order modifiers +dotnet_diagnostic.IDE0036.severity = warning + +# RCS1058: Use compound assignment. +dotnet_diagnostic.RCS1058.severity = warning + +# CA1848: Use the LoggerMessage delegates +dotnet_diagnostic.CA1848.severity = warning + +# S1939: Inheritance list should not be redundant +dotnet_diagnostic.S1939.severity = none + +# CA1812: An internal class is apparently never instantiated. If so, remove the code from the assembly. If this class is intended to contain only static members, make it 'static' (Module in Visual Basic). +dotnet_diagnostic.CA1812.severity = suggestion + +# S101: Types should be named in PascalCase +dotnet_diagnostic.S101.severity = silent + +# S907: "goto" statement should not be used +dotnet_diagnostic.S907.severity = silent + +# RCS1139: Add summary element to documentation comment. +dotnet_diagnostic.RCS1139.severity = none + +# S3358: Extract nested ternary operation into an independent statement. +dotnet_diagnostic.S3358.severity = none + +# RCS1242: Do not pass non-read-only struct by read-only reference. +dotnet_diagnostic.RCS1242.severity = silent + +# RCS1194: Implement exception constructors. +dotnet_diagnostic.RCS1194.severity = suggestion + +# S3925: "ISerializable" should be implemented correctly +dotnet_diagnostic.S3925.severity = suggestion + +# S1066: Collapsible "if" statements should be merged +dotnet_diagnostic.S1066.severity = suggestion + +# CA2225: Operator overloads have named alternates +dotnet_diagnostic.CA2225.severity = suggestion + +# CA1040: Avoid empty interfaces +dotnet_diagnostic.CA1040.severity = none + +# CA1308: Normalize string to uppercase +dotnet_diagnostic.CA1308.severity = none + +# S3267: Loops should be simplified with "LINQ" expression +dotnet_diagnostic.S3267.severity = suggestion + +# IDE0052: Remove unread private members +dotnet_diagnostic.IDE0052.severity = warning + +# S3440: Variables should not be checked against the values they're about to be assigned +dotnet_diagnostic.S3440.severity = none + +# S4144: Methods should not have identical implementations +dotnet_diagnostic.S4144.severity = warning + +# S927: Parameter names should match base declaration and other partial definitions +dotnet_diagnostic.S927.severity = error + +# CS8600: Converting null literal or possible null value to non-nullable type. +dotnet_diagnostic.CS8600.severity = error + +# CS8604: Possible null reference argument. +dotnet_diagnostic.CS8604.severity = error + +# S1133: Deprecated code should be removed +dotnet_diagnostic.S1133.severity = silent + +# S1135: Track uses of "TODO" tags +dotnet_diagnostic.S1135.severity = suggestion + +# S2094: Classes should not be empty +dotnet_diagnostic.S2094.severity = none + +# S112: General exceptions should never be thrown +dotnet_diagnostic.S112.severity = error + +# S2857: SQL keywords should be delimited by whitespace +dotnet_diagnostic.S2857.severity = silent + +# S6575: Use "TimeZoneInfo.FindSystemTimeZoneById" without converting the timezones with "TimezoneConverter" +dotnet_diagnostic.S6575.severity = silent + +# CS8625: Cannot convert null literal to non-nullable reference type. +dotnet_diagnostic.CS8625.severity = error + +# S2589: Boolean expressions should not be gratuitous +dotnet_diagnostic.S2589.severity = silent + +# IDE0049: Simplify Names +dotnet_diagnostic.IDE0049.severity = none diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj index bbd47cd..c98e99f 100644 --- a/Benchmarks/Benchmarks.csproj +++ b/Benchmarks/Benchmarks.csproj @@ -1,20 +1,21 @@ - + Exe - netcoreapp3.0 + net7.0 - - - - + + + + + - + diff --git a/Benchmarks/Program.cs b/Benchmarks/Program.cs index c41ac23..774e6f7 100644 --- a/Benchmarks/Program.cs +++ b/Benchmarks/Program.cs @@ -1,27 +1,61 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using System; +using System; +using System.Globalization; using System.IO; using System.Linq; using System.Text; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; namespace Benchmarks { - class Program { - static void Main(string[] args) { + public static class Program { + public static void Main(string[] args) { BenchmarkRunner.Run(); } } - [RPlotExporter, RankColumn] + [RPlotExporter, RankColumn, MemoryDiagnoser] + [SimpleJob(launchCount: 1, warmupCount: 3, iterationCount: 10)] public class Serialize { + private static readonly Csv.ISerializer V1Serializer = new Csv.Internal.NaiveImpl.NaiveSerializer(); + + private static readonly RecordParser.Parsers.IVariableLengthWriter RecordParserWriter = new RecordParser.Builders.Writer.VariableLengthWriterSequentialBuilder() + .Map(x => x.Bool) + .Skip(1) + .Map(x => x.Byte) + .Map(x => x.SByte) + .Skip(1) + .Map(x => x.Short) + .Skip(1) + .Map(x => x.UShort) + .Skip(1) + .Map(x => x.Int) + .Skip(1) + .Map(x => x.UInt) + .Skip(1) + .Map(x => x.Long) + .Skip(1) + .Map(x => x.ULong) + .Skip(1) + .Map(x => x.Float) + .Skip(1) + .Map(x => x.Double) + .Skip(1) + .Map(x => x.Decimal) + .Skip(1) + .Map(x => x.String) + .Skip(1) + .Map(x => x.DateTime) + .Skip(1) + .Build(","); + private Model[] _data; [Params(1000, 10000, 100000)] public int N; - + [GlobalSetup] public void Setup() { - Model item = new Model { + Model item = new() { Bool = true, Byte = 0x66, SByte = -100, @@ -41,31 +75,75 @@ public void Setup() { } [Benchmark] - public string CsvSerializerSerialize() => Csv.CsvSerializer.Serialize(_data); + public string CsvSerializerV1Serialize() { + StringBuilder stringBuilder = new(); + foreach (Model item in _data) { + V1Serializer.SerializeItem(null, ',', stringBuilder, item); + } + return stringBuilder.ToString().TrimEnd(); + } + + [Benchmark] + public string CsvSerializerV2Serialize() { + return Csv.CsvSerializer.Serialize(_data); + } [Benchmark] public string CsvHelperSerialize() { - using MemoryStream memoryStream = new MemoryStream(); - using StreamWriter streamWriter = new StreamWriter(memoryStream); - using CsvHelper.CsvWriter csvWriter = new CsvHelper.CsvWriter(streamWriter); + using MemoryStream memoryStream = new(); + using StreamWriter streamWriter = new(memoryStream); + using CsvHelper.CsvWriter csvWriter = new(streamWriter, CultureInfo.InvariantCulture); csvWriter.WriteRecords(_data); return Encoding.UTF8.GetString(memoryStream.ToArray()); } [Benchmark] - public byte[] MessagePackSerialize() => MessagePack.MessagePackSerializer.Serialize(_data); + public string MessagePackSerializeToJson() => MessagePack.MessagePackSerializer.SerializeToJson(_data); [Benchmark] - public string MessagePackSerializeToBase64() => Convert.ToBase64String(MessagePack.MessagePackSerializer.Serialize(_data)); + public string NewtonsoftJsonSerializeObject() => Newtonsoft.Json.JsonConvert.SerializeObject(_data); [Benchmark] - public string MessagePackSerializeToJson() => MessagePack.MessagePackSerializer.ToJson(_data); + public byte[] Utf8JsonSerialize() => Utf8Json.JsonSerializer.Serialize(_data); [Benchmark] - public string NewtonsoftJsonSerializeObject() => Newtonsoft.Json.JsonConvert.SerializeObject(_data); + public string RecordParserWrite() { + Span buffer = stackalloc char[2048]; + StringBuilder stringBuilder = new(); + foreach (Model item in _data) { + RecordParserWriter.TryFormat(item, buffer, out int charsWritten); + string s = new(buffer.Slice(0, charsWritten)); + stringBuilder.AppendLine(s); + } + return stringBuilder.ToString().TrimEnd(); + } + + [Benchmark(Baseline = true)] + public string SystemTextJsonSerialize() => System.Text.Json.JsonSerializer.Serialize(_data); [Benchmark] - public byte[] Utf8JsonSerialize() => Utf8Json.JsonSerializer.Serialize(_data); + public string StringJoin() => string.Join("\r\n", + from item in _data + select string.Join(',', + from v in new object[] { + item.Bool, + item.Byte, + item.SByte, + item.Short, + item.UShort, + item.Int, + item.UInt, + item.Long, + item.ULong, + item.Float, + item.Double, + item.Decimal, + item.String, + item.DateTime + } + select v.ToString() + ) + ); } [MessagePack.MessagePackObject] diff --git a/CsvSerializer.sln b/CsvSerializer.sln index 83b4128..ee90d58 100644 --- a/CsvSerializer.sln +++ b/CsvSerializer.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29209.152 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34221.43 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CsvSerializer", "CsvSerializer\CsvSerializer.csproj", "{580B2E33-309C-4E25-8BF4-E2D4BEABD54C}" EndProject @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Benchmarks\Be EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BF25372C-CDD9-4AA0-B4E8-570A90FB3ECD}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore README.md = README.md EndProjectSection EndProject diff --git a/CsvSerializer/CsvColumnAttribute.cs b/CsvSerializer/CsvColumnAttribute.cs deleted file mode 100644 index 24bf328..0000000 --- a/CsvSerializer/CsvColumnAttribute.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Csv { - [AttributeUsage(AttributeTargets.Property)] - public class CsvColumnAttribute : Attribute { - public string Name { get; } - public string? DateFormat { get; set; } - - public CsvColumnAttribute(string name) { - Name = name; - } - } -} diff --git a/CsvSerializer/CsvException.cs b/CsvSerializer/CsvException.cs deleted file mode 100644 index 2cd2010..0000000 --- a/CsvSerializer/CsvException.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Csv { - public abstract class CsvException : Exception { - public CsvException() { - } - - public CsvException(string? message) : base(message) { - } - - public CsvException(string? message, Exception? innerException) : base(message, innerException) { - } - } -} diff --git a/CsvSerializer/CsvFormatException.cs b/CsvSerializer/CsvFormatException.cs deleted file mode 100644 index be7116e..0000000 --- a/CsvSerializer/CsvFormatException.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace Csv { - public class CsvFormatException : CsvException { - public Type? Type { get; } - public string? PropertyName { get; } - public string? Value { get; } - - public CsvFormatException(string value, string? message) : base($"Cannot deserialize '{value}'. {message}") { - Value = value; - } - - public CsvFormatException(Type type, string value, string? message) : base($"Cannot deserialize '{value}' into {type.Name}. {message}") { - Type = type; - Value = value; - } - - public CsvFormatException(Type type, string propertyName, string value, string? message) : base($"Cannot deserialize '{value}' into {type.Name}.{propertyName}. {message}") { - Type = type; - PropertyName = propertyName; - Value = value; - } - - public CsvFormatException(Type type, string propertyName, string value, string? message, Exception? innerException) : base($"Cannot deserialize '{value}' into {type.Name}.{propertyName}. {message}", innerException) { - Type = type; - PropertyName = propertyName; - Value = value; - } - } -} diff --git a/CsvSerializer/CsvPropertyTypeException.cs b/CsvSerializer/CsvPropertyTypeException.cs deleted file mode 100644 index 844e3fb..0000000 --- a/CsvSerializer/CsvPropertyTypeException.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Csv { - public class CsvPropertyTypeException : CsvException { - public Type PropertyType { get; } - - public CsvPropertyTypeException(Type propertyType) : base($"Property of type {propertyType.Name} cannot be used in serialization or deserialization.") { - PropertyType = propertyType; - } - - public CsvPropertyTypeException(Type propertyType, string? message) : base($"Property of type {propertyType.Name} cannot be used in serialization or deserialization. {message}") { - PropertyType = propertyType; - } - - public CsvPropertyTypeException(Type propertyType, string? message, Exception? innerException) : base($"Property of type {propertyType.Name} cannot be used in serialization or deserialization. {message}", innerException) { - PropertyType = propertyType; - } - } -} diff --git a/CsvSerializer/CsvSerializer.cs b/CsvSerializer/CsvSerializer.cs deleted file mode 100644 index bc5315d..0000000 --- a/CsvSerializer/CsvSerializer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Security; -using System.Text; - -//#if DEBUG -[assembly: InternalsVisibleTo("Tests")] -//#endif -[assembly: InternalsVisibleTo("CsvSerializer.Dynamic")] -[assembly: AllowPartiallyTrustedCallers] -[assembly: SecurityTransparent] -[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)] -namespace Csv { - public static class CsvSerializer { - public static string Serialize(IEnumerable items, bool withHeaders = false, char delimiter = ',', IFormatProvider? provider = null) where T : notnull { - ISerializer serializer = Internal.NativeImpl.SerializerFactory.GetOrCreate(); - StringBuilder stringBuilder = new StringBuilder(); - if (withHeaders) { - serializer.SerializeHeader(delimiter, stringBuilder); - } - foreach (T item in items) { - serializer.SerializeItem(provider ?? CultureInfo.CurrentCulture, delimiter, stringBuilder, item); - } - return stringBuilder.ToString().TrimEnd(); - } - - public static T[] Deserialize(string csv, bool hasHeaders = false, char delimiter = ',', IFormatProvider? provider = null) where T : notnull { - IDeserializer deserializer = Internal.NativeImpl.DeserializerFactory.GetOrCreate(); - List items = deserializer.Deserialize(provider ?? CultureInfo.CurrentCulture, delimiter, hasHeaders, csv.AsMemory()); - return items.Cast().ToArray(); - } - } -} diff --git a/CsvSerializer/CsvSerializer.csproj b/CsvSerializer/CsvSerializer.csproj index d49fabc..600c57c 100644 --- a/CsvSerializer/CsvSerializer.csproj +++ b/CsvSerializer/CsvSerializer.csproj @@ -1,22 +1,30 @@  - netcoreapp3.0 + netstandard2.0 Csv - 8.0 + 11 enable true true RG.CsvSerializer Ronny Gunawan - (c) 2019 Ronny Gunawan + (c) 2019-2023 Ronny Gunawan LICENSE https://github.com/ronnygunawan/csv-serializer https://github.com/ronnygunawan/csv-serializer csv serializer deserializer parser core Fast CSV to object serializer and deserializer. - 1.0.8 + 2.0.0 + true + true + true + true + false + true + Generated + README.md @@ -31,56 +39,15 @@ True - - True - True - NumberConverters.tt - - - - - NETSTANDARD;NETSTANDARD2_0 - - - - NETCOREAPP;NETCOREAPP3_0 - - - - - - - - - - - - - - TextTemplatingFileGenerator - ConverterFactory.cs - - - TextTemplatingFileGenerator - NumberConverters.cs + + True + \ - - - - - - True - True - ConverterFactory.tt - - - True - True - NumberConverters.tt - + + diff --git a/CsvSerializer/CsvTypeException.cs b/CsvSerializer/CsvTypeException.cs deleted file mode 100644 index a9e1af6..0000000 --- a/CsvSerializer/CsvTypeException.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace Csv { - public class CsvTypeException : CsvException { - public Type Type { get; } - public string? PropertyName { get; } - - public CsvTypeException(Type type) : base($"{type.Name} cannot be used in serialization or deserialization.") { - Type = type; - } - - public CsvTypeException(Type type, string? message) : base($"{type.Name} cannot be used in serialization or deserialization. {message}") { - Type = type; - } - - public CsvTypeException(Type type, string propertyName, string? message) : base($"{type.Name}.{propertyName} cannot be used in serialization or deserialization. {message}") { - Type = type; - PropertyName = propertyName; - } - - public CsvTypeException(Type type, string propertyName, string? message, Exception? innerException) : base($"{type.Name}.{propertyName} cannot be used in serialization or deserialization. {message}", innerException) { - Type = type; - PropertyName = propertyName; - } - } -} diff --git a/CsvSerializer/Emitter/ILGeneratorExtensions.cs b/CsvSerializer/Emitter/ILGeneratorExtensions.cs deleted file mode 100644 index 2e3a67c..0000000 --- a/CsvSerializer/Emitter/ILGeneratorExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Missil; -using System.Reflection.Emit; - -namespace Csv.Emitter { - internal static class ILGeneratorExtensions { - public static ILGenerator Do(this ILGenerator gen, Action action) { action.Invoke(gen); return gen; } - public static ILGenerator DeclareLocalIf(this ILGenerator gen, bool condition, out LocalBuilder? local) { - if (condition) { - return gen.DeclareLocal(out local); - } else { - local = null; - return gen; - } - } - } -} diff --git a/CsvSerializer/Emitter/Methods.cs b/CsvSerializer/Emitter/Methods.cs deleted file mode 100644 index 8fffe9a..0000000 --- a/CsvSerializer/Emitter/Methods.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Text; -using Csv.Internal; - -namespace Csv.Emitter { - internal static class Methods { - public static int IndexOf(ReadOnlySpan span, char c) => span.IndexOf(c); - - public static MethodInfo StringSplitter_ReadNextLine = typeof(StringSplitter).GetMethod(nameof(StringSplitter.ReadNextLine), new Type[] { typeof(ReadOnlyMemory).MakeByRefType(), typeof(char) })!; - - public static MethodInfo List_String_get_Count = typeof(List).GetProperty(nameof(List.Count), BindingFlags.Public | BindingFlags.Instance)!.GetGetMethod()!; - public static MethodInfo List_String_get_Item = typeof(List).GetProperties(BindingFlags.Public | BindingFlags.Instance).Single(prop => prop.GetIndexParameters().Length > 0).GetGetMethod()!; - - public static MethodInfo List_object_Add = typeof(List).GetMethod(nameof(List.Add), new Type[] { typeof(object) })!; - - public static MethodInfo ReadOnlySpan_Char_get_Length = typeof(ReadOnlySpan).GetProperty(nameof(ReadOnlySpan.Length), BindingFlags.Public | BindingFlags.Instance)!.GetGetMethod()!; - public static MethodInfo ReadOnlySpan_Char_IndexOf = typeof(Methods).GetMethod(nameof(Methods.IndexOf), new Type[] { typeof(ReadOnlySpan), typeof(char) })!; - public static MethodInfo ReadOnlySpan_Char_ToString = typeof(ReadOnlySpan).GetMethod(nameof(ReadOnlySpan.ToString), Type.EmptyTypes)!; - public static MethodInfo ReadOnlySpan_Char_Slice = typeof(ReadOnlySpan).GetMethod(nameof(ReadOnlySpan.Slice), new Type[] { typeof(int), typeof(int) })!; - - public static MethodInfo Boolean_TryParse = typeof(bool).GetMethod(nameof(bool.TryParse), new Type[] { typeof(string), typeof(bool).MakeByRefType() })!; - public static MethodInfo Byte_TryParse = typeof(byte).GetMethod(nameof(byte.TryParse), new Type[] { typeof(string), typeof(byte).MakeByRefType() })!; - public static MethodInfo SByte_TryParse = typeof(sbyte).GetMethod(nameof(sbyte.TryParse), new Type[] { typeof(string), typeof(sbyte).MakeByRefType() })!; - public static MethodInfo Int16_TryParse = typeof(short).GetMethod(nameof(short.TryParse), new Type[] { typeof(string), typeof(short).MakeByRefType() })!; - public static MethodInfo UInt16_TryParse = typeof(ushort).GetMethod(nameof(ushort.TryParse), new Type[] { typeof(string), typeof(ushort).MakeByRefType() })!; - public static MethodInfo Int32_TryParse = typeof(int).GetMethod(nameof(int.TryParse), new Type[] { typeof(string), typeof(int).MakeByRefType() })!; - public static MethodInfo UInt32_TryParse = typeof(uint).GetMethod(nameof(uint.TryParse), new Type[] { typeof(string), typeof(uint).MakeByRefType() })!; - public static MethodInfo Int64_TryParse = typeof(long).GetMethod(nameof(long.TryParse), new Type[] { typeof(string), typeof(long).MakeByRefType() })!; - public static MethodInfo UInt64_TryParse = typeof(ulong).GetMethod(nameof(ulong.TryParse), new Type[] { typeof(string), typeof(ulong).MakeByRefType() })!; - public static MethodInfo Single_TryParse = typeof(float).GetMethod(nameof(float.TryParse), new Type[] { typeof(string), typeof(float).MakeByRefType() })!; - public static MethodInfo Double_TryParse = typeof(double).GetMethod(nameof(double.TryParse), new Type[] { typeof(string), typeof(double).MakeByRefType() })!; - public static MethodInfo Decimal_TryParse = typeof(decimal).GetMethod(nameof(decimal.TryParse), new Type[] { typeof(string), typeof(decimal).MakeByRefType() })!; - public static MethodInfo DateTime_TryParse = typeof(DateTime).GetMethod(nameof(DateTime.TryParse), new Type[] { typeof(string), typeof(DateTime).MakeByRefType() })!; - public static MethodInfo DateTime_TryParseExact = typeof(DateTime).GetMethod(nameof(DateTime.TryParseExact), new Type[] { typeof(string), typeof(string), typeof(IFormatProvider), typeof(DateTimeStyles), typeof(DateTime).MakeByRefType() })!; - - public static MethodInfo String_Trim = typeof(string).GetMethod(nameof(string.Trim), Type.EmptyTypes)!; - public static MethodInfo String_get_Length = typeof(string).GetProperty(nameof(string.Length), BindingFlags.Public | BindingFlags.Instance)!.GetGetMethod()!; - public static MethodInfo String_Substring = typeof(string).GetMethod(nameof(string.Substring), new Type[] { typeof(int), typeof(int) })!; - public static MethodInfo String_Replace = typeof(string).GetMethod(nameof(string.Replace), new Type[] { typeof(string), typeof(string) })!; - public static MethodInfo String_IsNullOrWhiteSpace = typeof(string).GetMethod(nameof(string.IsNullOrWhiteSpace), new Type[] { typeof(string) })!; - public static MethodInfo String_StartsWith = typeof(string).GetMethod(nameof(string.StartsWith), new Type[] { typeof(char) })!; - public static MethodInfo String_EndsWith = typeof(string).GetMethod(nameof(string.EndsWith), new Type[] { typeof(char) })!; - - public static ConstructorInfo CsvFormatException_ctor2 = typeof(CsvFormatException).GetConstructor(new Type[] { typeof(string), typeof(string) })!; - public static ConstructorInfo CsvFormatException_ctor3 = typeof(CsvFormatException).GetConstructor(new Type[] { typeof(Type), typeof(string), typeof(string) })!; - public static ConstructorInfo CsvFormatException_ctor4 = typeof(CsvFormatException).GetConstructor(new Type[] { typeof(Type), typeof(string), typeof(string), typeof(string) })!; - - public static MethodInfo StringBuilder_Append_Char = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(char) })!; - public static MethodInfo StringBuilder_Append_String = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(string) })!; - public static MethodInfo StringBuilder_Append_Boolean = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(bool) })!; - public static MethodInfo StringBuilder_Append_Byte = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(byte) })!; - public static MethodInfo StringBuilder_Append_SByte = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(sbyte) })!; - public static MethodInfo StringBuilder_Append_Int16 = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(short) })!; - public static MethodInfo StringBuilder_Append_UInt16 = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(ushort) })!; - public static MethodInfo StringBuilder_Append_Int32 = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(int) })!; - public static MethodInfo StringBuilder_Append_UInt32 = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(uint) })!; - public static MethodInfo StringBuilder_Append_Int64 = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(long) })!; - public static MethodInfo StringBuilder_Append_UInt64 = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(ulong) })!; - public static MethodInfo StringBuilder_Append_Single = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(float) })!; - public static MethodInfo StringBuilder_Append_Double = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(double) })!; - public static MethodInfo StringBuilder_Append_Decimal = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(decimal) })!; - public static MethodInfo StringBuilder_Append_Object = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new Type[] { typeof(object) })!; - - public static MethodInfo Boolean_ToString = typeof(bool).GetMethod(nameof(bool.ToString), Type.EmptyTypes)!; - public static MethodInfo Byte_ToString = typeof(byte).GetMethod(nameof(byte.ToString), Type.EmptyTypes)!; - public static MethodInfo SByte_ToString = typeof(sbyte).GetMethod(nameof(sbyte.ToString), Type.EmptyTypes)!; - public static MethodInfo Int16_ToString = typeof(short).GetMethod(nameof(short.ToString), Type.EmptyTypes)!; - public static MethodInfo UInt16_ToString = typeof(ushort).GetMethod(nameof(ushort.ToString), Type.EmptyTypes)!; - public static MethodInfo Int32_ToString = typeof(int).GetMethod(nameof(int.ToString), Type.EmptyTypes)!; - public static MethodInfo UInt32_ToString = typeof(uint).GetMethod(nameof(uint.ToString), Type.EmptyTypes)!; - public static MethodInfo Int64_ToString = typeof(long).GetMethod(nameof(long.ToString), Type.EmptyTypes)!; - public static MethodInfo UInt64_ToString = typeof(ulong).GetMethod(nameof(ulong.ToString), Type.EmptyTypes)!; - public static MethodInfo Single_ToString = typeof(float).GetMethod(nameof(float.ToString), Type.EmptyTypes)!; - public static MethodInfo Double_ToString = typeof(double).GetMethod(nameof(double.ToString), Type.EmptyTypes)!; - public static MethodInfo Decimal_ToString = typeof(decimal).GetMethod(nameof(decimal.ToString), Type.EmptyTypes)!; - public static MethodInfo DateTime_ToString = typeof(DateTime).GetMethod(nameof(DateTime.ToString), Type.EmptyTypes)!; - public static MethodInfo DateTime_ToString_Format = typeof(DateTime).GetMethod(nameof(DateTime.ToString), new Type[] { typeof(string) })!; - public static MethodInfo Uri_ToString = typeof(Uri).GetMethod(nameof(Uri.ToString), Type.EmptyTypes)!; - public static MethodInfo Object_ToString = typeof(object).GetMethod(nameof(object.ToString), Type.EmptyTypes)!; - - public static MethodInfo NullableDateTime_get_HasValue = typeof(DateTime?).GetProperty(nameof(Nullable.HasValue), BindingFlags.Public | BindingFlags.Instance)!.GetGetMethod()!; - public static MethodInfo NullableDateTime_get_Value = typeof(DateTime?).GetProperty(nameof(Nullable.Value), BindingFlags.Public | BindingFlags.Instance)!.GetGetMethod()!; - } -} diff --git a/CsvSerializer/Internal/ConverterEmitterAttribute.cs b/CsvSerializer/Internal/ConverterEmitterAttribute.cs deleted file mode 100644 index 9bd3927..0000000 --- a/CsvSerializer/Internal/ConverterEmitterAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Csv.Internal { - [AttributeUsage(AttributeTargets.Method)] - internal class ConverterEmitterAttribute : Attribute { - public Type? PrimaryLocalType { get; } - public Type? SecondaryLocalType { get; } - public bool GenericParameterIsPrimaryLocalType { get; } - public bool NullableOfGenericParameterIsPrimaryLocalType { get; } - - public ConverterEmitterAttribute( - Type? primaryLocalType = null, - Type? secondaryLocalType = null, - bool genericParameterIsPrimaryLocalType = false, - bool nullableOfGenericParameterIsPrimaryLocalType = false - ) { - PrimaryLocalType = primaryLocalType; - SecondaryLocalType = secondaryLocalType; - GenericParameterIsPrimaryLocalType = genericParameterIsPrimaryLocalType; - NullableOfGenericParameterIsPrimaryLocalType = nullableOfGenericParameterIsPrimaryLocalType; - } - } -} diff --git a/CsvSerializer/Internal/ConverterFactory.cs b/CsvSerializer/Internal/ConverterFactory.cs deleted file mode 100644 index bfecff8..0000000 --- a/CsvSerializer/Internal/ConverterFactory.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Csv.Internal.Converters; -using System; -using System.Collections.Concurrent; - -namespace Csv.Internal { - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | ConveterFactory.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal static class ConverterFactory { - private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - - public static IConverterEmitter GetOrCreateEmitter(Type type) { - return (IConverterEmitter)_cache.GetOrAdd(type, type => { - if (type == typeof(string)) { - return new StringConverter(); - } else if (type.IsEnum) { - return Activator.CreateInstance(typeof(EnumConverter<>).MakeGenericType(type))!; - } else if (Nullable.GetUnderlyingType(type) is Type t && t.IsEnum) { - return Activator.CreateInstance(typeof(NullableEnumConverter<>).MakeGenericType(t))!; - } else if (type == typeof(Boolean)) { - return new BooleanConverter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(Boolean)) { - return new NullableBooleanConverter(); - } else if (type == typeof(Byte)) { - return new ByteConverter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(Byte)) { - return new NullableByteConverter(); - } else if (type == typeof(SByte)) { - return new SByteConverter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(SByte)) { - return new NullableSByteConverter(); - } else if (type == typeof(Int16)) { - return new Int16Converter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(Int16)) { - return new NullableInt16Converter(); - } else if (type == typeof(UInt16)) { - return new UInt16Converter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(UInt16)) { - return new NullableUInt16Converter(); - } else if (type == typeof(Int32)) { - return new Int32Converter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(Int32)) { - return new NullableInt32Converter(); - } else if (type == typeof(UInt32)) { - return new UInt32Converter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(UInt32)) { - return new NullableUInt32Converter(); - } else if (type == typeof(Int64)) { - return new Int64Converter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(Int64)) { - return new NullableInt64Converter(); - } else if (type == typeof(UInt64)) { - return new UInt64Converter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(UInt64)) { - return new NullableUInt64Converter(); - } else if (type == typeof(Single)) { - return new SingleConverter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(Single)) { - return new NullableSingleConverter(); - } else if (type == typeof(Double)) { - return new DoubleConverter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(Double)) { - return new NullableDoubleConverter(); - } else if (type == typeof(Decimal)) { - return new DecimalConverter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(Decimal)) { - return new NullableDecimalConverter(); - } else if (type == typeof(DateTime)) { - return new DateTimeConverter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(DateTime)) { - return new NullableDateTimeConverter(); - } else if (type == typeof(Uri)) { - return new UriConverter(); - } else { - throw new NotImplementedException(); - } - }); - } - - public static INativeConverter GetOrCreate() where T : notnull => (INativeConverter)GetOrCreateEmitter(typeof(T)); - } -} diff --git a/CsvSerializer/Internal/ConverterFactory.tt b/CsvSerializer/Internal/ConverterFactory.tt deleted file mode 100644 index 2e84ffa..0000000 --- a/CsvSerializer/Internal/ConverterFactory.tt +++ /dev/null @@ -1,61 +0,0 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ output extension=".cs" #> -<# - string[] structTypes = { - "Boolean", - "Byte", - "SByte", - "Int16", - "UInt16", - "Int32", - "UInt32", - "Int64", - "UInt64", - "Single", - "Double", - "Decimal", - "DateTime" - }; -#> -using Csv.Internal.Converters; -using System; -using System.Collections.Concurrent; - -namespace Csv.Internal { - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | ConveterFactory.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal static class ConverterFactory { - private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - - public static IConverterEmitter GetOrCreateEmitter(Type type) { - return (IConverterEmitter)_cache.GetOrAdd(type, type => { - if (type == typeof(string)) { - return new StringConverter(); - } else if (type.IsEnum) { - return Activator.CreateInstance(typeof(EnumConverter<>).MakeGenericType(type))!; - } else if (Nullable.GetUnderlyingType(type) is Type t && t.IsEnum) { - return Activator.CreateInstance(typeof(NullableEnumConverter<>).MakeGenericType(t))!; -<# foreach (string structType in structTypes) { #> - } else if (type == typeof(<#= structType #>)) { - return new <#= structType #>Converter(); - } else if (Nullable.GetUnderlyingType(type) == typeof(<#= structType #>)) { - return new Nullable<#= structType #>Converter(); -<# } #> - } else if (type == typeof(Uri)) { - return new UriConverter(); - } else { - throw new NotImplementedException(); - } - }); - } - - public static INativeConverter GetOrCreate() where T : notnull => (INativeConverter)GetOrCreateEmitter(typeof(T)); - } -} diff --git a/CsvSerializer/Internal/Converters/BooleanConverter.cs b/CsvSerializer/Internal/Converters/BooleanConverter.cs deleted file mode 100644 index fe4283a..0000000 --- a/CsvSerializer/Internal/Converters/BooleanConverter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Missil; -using System; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.Converters { - internal class BooleanConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, bool value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public bool Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return bool.Parse(text.Span); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(bool)); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Call("Parse", typeof(ReadOnlySpan)); - } -} diff --git a/CsvSerializer/Internal/Converters/DateTimeConverter.cs b/CsvSerializer/Internal/Converters/DateTimeConverter.cs deleted file mode 100644 index 5291acd..0000000 --- a/CsvSerializer/Internal/Converters/DateTimeConverter.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Globalization; -using System.Reflection.Emit; -using System.Text; -using Missil; - -namespace Csv.Internal.Converters { - internal class DateTimeConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, DateTime value, CsvColumnAttribute? attribute, char delimiter) { - string text = attribute?.DateFormat switch { - string dateFormat => value.ToString(dateFormat, provider), - _ => value.ToString(provider) - }; - bool containsQuote = text.Contains('\"'); - if (containsQuote) { - text = text.Replace("\"", "\"\""); - } - if (containsQuote || text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } - } - - public DateTime Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (attribute?.DateFormat is string dateFormat) { - return DateTime.ParseExact(text.Span, dateFormat.AsSpan(), provider, DateTimeStyles.AssumeLocal); - } else { - return DateTime.Parse(text.Span, provider, DateTimeStyles.AssumeLocal); - } - } - - /// A local of type - [ConverterEmitter(typeof(DateTime))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Emit(gen => attribute?.DateFormat switch { - string dateFormat => gen - .Ldloca(local!) - .Ldstr(dateFormat) - .Ldarg_1() - .Call("ToString", typeof(string), typeof(IFormatProvider)), - _ => gen - .Ldloca(local!) - .Ldarg_1() - .Call("ToString", typeof(IFormatProvider)) - }) - .Dup() - .Ldc_I4_X((int)'"') - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label doesNotContainQuote) - .Ldstr("\"") - .Ldstr("\"\"") - .Call("Replace", typeof(string), typeof(string)) - .Br_S(out Label appendQuotedString) - .Label(doesNotContainQuote) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label doesNotContainDelimiter) - .Label(appendQuotedString) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(out Label end) - .Label(doesNotContainDelimiter) - .Call("Append", typeof(string)) - .Label(end); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Emit(gen => attribute?.DateFormat switch { - string dateFormat => gen - .CallPropertyGet>("Span") - .Ldstr(dateFormat) - .Call(typeof(MemoryExtensions).GetMethod("AsSpan", new Type[] { typeof(string) })!) - .Ldarg_1() - .Ldc_I4_X((int)DateTimeStyles.AssumeLocal) - .Call("ParseExact", typeof(ReadOnlySpan), typeof(ReadOnlySpan), typeof(IFormatProvider), typeof(DateTimeStyles)), - _ => gen - .CallPropertyGet>("Span") - .Ldarg_1() - .Ldc_I4_X((int)DateTimeStyles.AssumeLocal) - .Call("Parse", typeof(ReadOnlySpan), typeof(IFormatProvider), typeof(DateTimeStyles)) - }); - } -} diff --git a/CsvSerializer/Internal/Converters/EnumConverter.cs b/CsvSerializer/Internal/Converters/EnumConverter.cs deleted file mode 100644 index 9507c16..0000000 --- a/CsvSerializer/Internal/Converters/EnumConverter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Missil; -using System; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.Converters { - internal class EnumConverter : INativeConverter where TEnum : struct, Enum { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, TEnum value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public TEnum Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return Enum.Parse(text.ToString()); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Box() - .Callvirt("Append", typeof(object)); - - /// A local of type - [ConverterEmitter(secondaryLocalType: typeof(string))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Callvirt>("ToString") - .Stloc(secondaryLocal!) - .Ldtoken() - .Ldloc(secondaryLocal!) - .Call("Parse", typeof(Type), typeof(string)) - .Unbox_Any(); - } -} diff --git a/CsvSerializer/Internal/Converters/NullableBooleanConverter.cs b/CsvSerializer/Internal/Converters/NullableBooleanConverter.cs deleted file mode 100644 index 65d44ed..0000000 --- a/CsvSerializer/Internal/Converters/NullableBooleanConverter.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Missil; -using System; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.Converters { - internal class NullableBooleanConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, bool? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public bool? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return bool.Parse(text.Span); - } - } - - /// A local of type - [ConverterEmitter(typeof(bool?))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Callvirt("Append", typeof(bool)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(bool?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Call("Parse", typeof(ReadOnlySpan)) - .Newobj(typeof(bool)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } -} diff --git a/CsvSerializer/Internal/Converters/NullableDateTimeConverter.cs b/CsvSerializer/Internal/Converters/NullableDateTimeConverter.cs deleted file mode 100644 index 0811380..0000000 --- a/CsvSerializer/Internal/Converters/NullableDateTimeConverter.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Missil; -using System; -using System.Globalization; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.Converters { - internal class NullableDateTimeConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, DateTime? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - string text = attribute?.DateFormat switch { - string dateFormat => value.Value.ToString(dateFormat, provider), - _ => value.Value.ToString(provider) - }; - bool containsQuote = text.Contains('\"'); - if (containsQuote) { - text = text.Replace("\"", "\"\""); - } - if (containsQuote || text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } - } - } - - public DateTime? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) return null; - if (attribute?.DateFormat is string dateFormat) { - return DateTime.ParseExact(text.Span, dateFormat.AsSpan(), provider, DateTimeStyles.AssumeLocal); - } else { - return DateTime.Parse(text.Span, provider, DateTimeStyles.AssumeLocal); - } - } - - /// A local of type - /// A local of type - [ConverterEmitter(typeof(DateTime?), typeof(DateTime))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? nullableLocal, LocalBuilder? local, CsvColumnAttribute? attribute) => gen - .Stloc(nullableLocal!) - .Ldloca(nullableLocal!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label end) - .Ldloca(nullableLocal!) - .CallPropertyGet("Value") - .Stloc(local!) - .Emit(gen => attribute?.DateFormat switch { - string dateFormat => gen - .Ldloca(local!) - .Ldstr(dateFormat) - .Ldarg_1() - .Call("ToString", typeof(string), typeof(IFormatProvider)), - _ => gen - .Ldloca(local!) - .Ldarg_1() - .Call("ToString", typeof(IFormatProvider)) - }) - .Dup() - .Ldc_I4_X((int)'"') - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label doesNotContainQuote) - .Ldstr("\"") - .Ldstr("\"\"") - .Call("Replace", typeof(string), typeof(string)) - .Br_S(out Label appendQuotedString) - .Label(doesNotContainQuote) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label doesNotContainDelimiter) - .Label(appendQuotedString) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(out Label endAppend) - .Label(doesNotContainDelimiter) - .Call("Append", typeof(string)) - .Label(endAppend) - .Label(end); - - /// A local of type - [ConverterEmitter(typeof(DateTime?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .Emit(gen => attribute?.DateFormat switch { - string dateFormat => gen - .CallPropertyGet>("Span") - .Ldstr(dateFormat) - .Call(typeof(MemoryExtensions).GetMethod("AsSpan", new Type[] { typeof(string) })!) - .Ldarg_1() - .Ldc_I4_X((int)DateTimeStyles.AssumeLocal) - .Call("ParseExact", typeof(ReadOnlySpan), typeof(ReadOnlySpan), typeof(IFormatProvider), typeof(DateTimeStyles)), - _ => gen - .CallPropertyGet>("Span") - .Ldarg_1() - .Ldc_I4_X((int)DateTimeStyles.AssumeLocal) - .Call("Parse", typeof(ReadOnlySpan), typeof(IFormatProvider), typeof(DateTimeStyles)) - }) - .Newobj(typeof(DateTime)) - .Stloc(local!) - .Br_S(out Label endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(endif) - .Ldloc(local!); - } -} diff --git a/CsvSerializer/Internal/Converters/NullableEnumConverter.cs b/CsvSerializer/Internal/Converters/NullableEnumConverter.cs deleted file mode 100644 index 9a00746..0000000 --- a/CsvSerializer/Internal/Converters/NullableEnumConverter.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Missil; -using System; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.Converters { - internal class NullableEnumConverter : INativeConverter where TEnum : struct, Enum { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, TEnum? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public TEnum? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) return null; - return Enum.Parse(text.ToString()); - } - - /// A local of type - [ConverterEmitter(nullableOfGenericParameterIsPrimaryLocalType: true)] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? nullableLocal, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Stloc(nullableLocal!) - .Ldloca(nullableLocal!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label end) - .Ldloca(nullableLocal!) - .CallPropertyGet("Value") - .Box() - .Callvirt("Append", typeof(object)) - .Label(end); - - /// A local of type - /// A local of type - [ConverterEmitter(nullableOfGenericParameterIsPrimaryLocalType: true, secondaryLocalType: typeof(string))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? nullableLocal, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .Callvirt>("ToString") - .Stloc(secondaryLocal!) - .Ldtoken() - .Ldloc(secondaryLocal!) - .Call("Parse", typeof(Type), typeof(string)) - .Unbox_Any() - .Newobj(typeof(TEnum)) - .Stloc(nullableLocal!) - .Br_S(out Label endif) - .Label(@else) - .Pop() - .Ldloca(nullableLocal!) - .Initobj() - .Label(endif) - .Ldloc(nullableLocal!); - } -} diff --git a/CsvSerializer/Internal/Converters/NumberConverters.cs b/CsvSerializer/Internal/Converters/NumberConverters.cs deleted file mode 100644 index 9289c42..0000000 --- a/CsvSerializer/Internal/Converters/NumberConverters.cs +++ /dev/null @@ -1,1017 +0,0 @@ -using Missil; -using System; -using System.Globalization; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.Converters { - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class ByteConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Byte value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public Byte Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return Byte.Parse(text.Span, provider: provider); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(Byte)); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableByteConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Byte? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public Byte? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return Byte.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - [ConverterEmitter(typeof(Byte?))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Callvirt("Append", typeof(Byte)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(Byte?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(Byte)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class SByteConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, SByte value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public SByte Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return SByte.Parse(text.Span, provider: provider); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(SByte)); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableSByteConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, SByte? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public SByte? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return SByte.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - [ConverterEmitter(typeof(SByte?))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Callvirt("Append", typeof(SByte)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(SByte?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(SByte)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class Int16Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Int16 value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public Int16 Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return Int16.Parse(text.Span, provider: provider); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(Int16)); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableInt16Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Int16? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public Int16? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return Int16.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - [ConverterEmitter(typeof(Int16?))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Callvirt("Append", typeof(Int16)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(Int16?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(Int16)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class UInt16Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, UInt16 value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public UInt16 Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return UInt16.Parse(text.Span, provider: provider); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(UInt16)); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableUInt16Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, UInt16? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public UInt16? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return UInt16.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - [ConverterEmitter(typeof(UInt16?))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Callvirt("Append", typeof(UInt16)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(UInt16?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(UInt16)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class Int32Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Int32 value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public Int32 Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return Int32.Parse(text.Span, provider: provider); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(Int32)); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableInt32Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Int32? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public Int32? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return Int32.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - [ConverterEmitter(typeof(Int32?))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Callvirt("Append", typeof(Int32)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(Int32?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(Int32)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class UInt32Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, UInt32 value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public UInt32 Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return UInt32.Parse(text.Span, provider: provider); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(UInt32)); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableUInt32Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, UInt32? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public UInt32? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return UInt32.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - [ConverterEmitter(typeof(UInt32?))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Callvirt("Append", typeof(UInt32)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(UInt32?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(UInt32)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class Int64Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Int64 value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public Int64 Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return Int64.Parse(text.Span, provider: provider); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(Int64)); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableInt64Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Int64? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public Int64? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return Int64.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - [ConverterEmitter(typeof(Int64?))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Callvirt("Append", typeof(Int64)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(Int64?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(Int64)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class UInt64Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, UInt64 value, CsvColumnAttribute? attribute, char delimiter) { - stringBuilder.Append(value); - } - - public UInt64 Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return UInt64.Parse(text.Span, provider: provider); - } - - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(UInt64)); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableUInt64Converter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, UInt64? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - stringBuilder.Append(value.Value); - } - } - - public UInt64? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return UInt64.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - [ConverterEmitter(typeof(UInt64?))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Callvirt("Append", typeof(UInt64)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(UInt64?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Integer) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(UInt64)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class SingleConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Single value, CsvColumnAttribute? attribute, char delimiter) { - string text = value.ToString(provider); - if (text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } - } - - public Single Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return Single.Parse(text.Span, provider: provider); - } - - /// A local of type - [ConverterEmitter(typeof(Single))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .Ldarg_1() - .Call("ToString", typeof(IFormatProvider)) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label @else) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(out Label endif) - .Label(@else) - .Call("Append", typeof(string)) - .Label(endif); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Float) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableSingleConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Single? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - string text = value.Value.ToString(provider); - if (text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } - } - } - - public Single? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return Single.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - /// A local of type - [ConverterEmitter(typeof(Single?), typeof(Single))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Stloc(secondaryLocal!) - .Ldloca(secondaryLocal!) - .Ldarg_1() - .Call("ToString", typeof(IFormatProvider)) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label @else) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(@endif) - .Label(@else) - .Call("Append", typeof(string)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(Single?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Float) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(Single)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class DoubleConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Double value, CsvColumnAttribute? attribute, char delimiter) { - string text = value.ToString(provider); - if (text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } - } - - public Double Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return Double.Parse(text.Span, provider: provider); - } - - /// A local of type - [ConverterEmitter(typeof(Double))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .Ldarg_1() - .Call("ToString", typeof(IFormatProvider)) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label @else) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(out Label endif) - .Label(@else) - .Call("Append", typeof(string)) - .Label(endif); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Float) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableDoubleConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Double? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - string text = value.Value.ToString(provider); - if (text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } - } - } - - public Double? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return Double.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - /// A local of type - [ConverterEmitter(typeof(Double?), typeof(Double))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Stloc(secondaryLocal!) - .Ldloca(secondaryLocal!) - .Ldarg_1() - .Call("ToString", typeof(IFormatProvider)) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label @else) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(@endif) - .Label(@else) - .Call("Append", typeof(string)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(Double?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Float) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(Double)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class DecimalConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Decimal value, CsvColumnAttribute? attribute, char delimiter) { - string text = value.ToString(provider); - if (text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } - } - - public Decimal Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return Decimal.Parse(text.Span, provider: provider); - } - - /// A local of type - [ConverterEmitter(typeof(Decimal))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .Ldarg_1() - .Call("ToString", typeof(IFormatProvider)) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label @else) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(out Label endif) - .Label(@else) - .Call("Append", typeof(string)) - .Label(endif); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Currency) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class NullableDecimalConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Decimal? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { - string text = value.Value.ToString(provider); - if (text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } - } - } - - public Decimal? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return Decimal.Parse(text.Span, provider: provider); - } - } - - - /// A local of type - /// A local of type - [ConverterEmitter(typeof(Decimal?), typeof(Decimal))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet("HasValue") - .Brfalse_S(out Label @endif) - .Ldloca(local!) - .CallPropertyGet("Value") - .Stloc(secondaryLocal!) - .Ldloca(secondaryLocal!) - .Ldarg_1() - .Call("ToString", typeof(IFormatProvider)) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label @else) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(@endif) - .Label(@else) - .Call("Append", typeof(string)) - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(Decimal?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") - .Ldc_I4_X((int)NumberStyles.Currency) - .Ldarg_1() - .Call("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj(typeof(Decimal)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj() - .Label(@endif) - .Ldloc(local!); - } - -} \ No newline at end of file diff --git a/CsvSerializer/Internal/Converters/NumberConverters.tt b/CsvSerializer/Internal/Converters/NumberConverters.tt deleted file mode 100644 index 3cb92de..0000000 --- a/CsvSerializer/Internal/Converters/NumberConverters.tt +++ /dev/null @@ -1,190 +0,0 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ output extension=".cs" #> -<# - string[] types = { - "Byte", - "SByte", - "Int16", - "UInt16", - "Int32", - "UInt32", - "Int64", - "UInt64", - "Single", - "Double", - "Decimal" - }; -#> -using Missil; -using System; -using System.Globalization; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.Converters { -<# foreach(string type in types) { #> - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class <#= type #>Converter : INativeConverter<<#= type #>> { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, <#= type #> value, CsvColumnAttribute? attribute, char delimiter) { -<# if (type == "Single" || type == "Double" || type == "Decimal") { #> - string text = value.ToString(provider); - if (text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } -<# } else { #> - stringBuilder.Append(value); -<# } #> - } - - public <#= type #> Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - return <#= type #>.Parse(text.Span, provider: provider); - } - -<# if (type == "Single" || type == "Double" || type == "Decimal") { #> - /// A local of type - [ConverterEmitter(typeof(<#= type #>))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .Ldarg_1() - .Call<<#= type #>>("ToString", typeof(IFormatProvider)) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label @else) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(out Label endif) - .Label(@else) - .Call("Append", typeof(string)) - .Label(endif); -<# } else { #> - [ConverterEmitter] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Call("Append", typeof(<#= type #>)); -<# } #> - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .CallPropertyGet>("Span") -<# if (type == "Single" || type == "Double") { #> - .Ldc_I4_X((int)NumberStyles.Float) -<# } else if (type == "Decimal") { #> - .Ldc_I4_X((int)NumberStyles.Currency) -<# } else { #> - .Ldc_I4_X((int)NumberStyles.Integer) -<# } #> - .Ldarg_1() - .Call<<#= type #>>("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)); - } - - /*************************************** - | THIS CLASS WAS AUTO GENERATED USING | - | NumberConverters.tt TEXT TEMPLATE. | - | DO NOT MODIFY THIS CLASS!!! | - ***************************************/ - internal class Nullable<#= type #>Converter : INativeConverter<<#= type #>?> { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, <#= type #>? value, CsvColumnAttribute? attribute, char delimiter) { - if (value.HasValue) { -<# if (type == "Single" || type == "Double" || type == "Decimal") { #> - string text = value.Value.ToString(provider); - if (text.Contains(delimiter)) { - stringBuilder.Append('"').Append(text).Append('"'); - } else { - stringBuilder.Append(text); - } -<# } else { #> - stringBuilder.Append(value.Value); -<# } #> - } - } - - public <#= type #>? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return <#= type #>.Parse(text.Span, provider: provider); - } - } - - - /// A local of type -<# if (type == "Single" || type == "Double" || type == "Decimal") { #> - /// A local of type - [ConverterEmitter(typeof(<#= type #>?), typeof(<#= type #>))] -<# } else { #> - [ConverterEmitter(typeof(<#= type #>?))] -<# } #> - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute) => gen - .Stloc(local!) - .Ldloca(local!) - .CallvirtPropertyGet<<#= type #>?>("HasValue") - .Brfalse_S(out Label @endif) -<# if (type == "Single" || type == "Double" || type == "Decimal") { #> - .Ldloca(local!) - .CallPropertyGet<<#= type #>?>("Value") - .Stloc(secondaryLocal!) - .Ldloca(secondaryLocal!) - .Ldarg_1() - .Call<<#= type #>>("ToString", typeof(IFormatProvider)) - .Dup() - .Ldarg_2() - .Callvirt("Contains", typeof(char)) - .Brfalse_S(out Label @else) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Call("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Call("Append", typeof(char)) - .Br_S(@endif) - .Label(@else) - .Call("Append", typeof(string)) -<# } else { #> - .Ldloca(local!) - .CallPropertyGet<<#= type #>?>("Value") - .Callvirt("Append", typeof(<#= type #>)) -<# } #> - .Label(@endif); - - /// A local of type - [ConverterEmitter(typeof(<#= type #>?))] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .CallPropertyGet>("Span") -<# if (type == "Single" || type == "Double") { #> - .Ldc_I4_X((int)NumberStyles.Float) -<# } else if (type == "Decimal") { #> - .Ldc_I4_X((int)NumberStyles.Currency) -<# } else { #> - .Ldc_I4_X((int)NumberStyles.Integer) -<# } #> - .Ldarg_1() - .Call<<#= type #>>("Parse", typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider)) - .Newobj<<#= type #>?>(typeof(<#= type #>)) - .Stloc(local!) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldloca(local!) - .Initobj<<#= type #>?>() - .Label(@endif) - .Ldloc(local!); - } - -<# } #> -} \ No newline at end of file diff --git a/CsvSerializer/Internal/Converters/StringConverter.cs b/CsvSerializer/Internal/Converters/StringConverter.cs deleted file mode 100644 index 1f2cbc4..0000000 --- a/CsvSerializer/Internal/Converters/StringConverter.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Missil; -using System; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.Converters { - internal class StringConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, string? value, CsvColumnAttribute? attribute, char delimiter) { - if (value is { }) { - stringBuilder.Append('"').Append(value.Replace("\"", "\"\"")).Append('"'); - } - } - - public string? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return string.Empty; - } else { - return text.ToString(); - } - } - - /// A local of type - [ConverterEmitter(typeof(string))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .Ldnull() - .Cgt_Un() - .Brfalse_S(out Label @else) - .Stloc(local!) - .Ldc_I4_X((int)'"') - .Callvirt("Append", typeof(char)) - .Ldloc(local!) - .Callvirt("ToString") - .Ldstr("\"") - .Ldstr("\"\"") - .Callvirt("Replace", typeof(string), typeof(string)) - .Callvirt("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Callvirt("Append", typeof(char)) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Label(@endif); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .Callvirt>("ToString") - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldsfld(typeof(string).GetField("Empty")!) - .Label(@endif); - } -} diff --git a/CsvSerializer/Internal/Converters/UriConverter.cs b/CsvSerializer/Internal/Converters/UriConverter.cs deleted file mode 100644 index d6b5011..0000000 --- a/CsvSerializer/Internal/Converters/UriConverter.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Missil; -using System; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.Converters { - internal class UriConverter : INativeConverter { - public void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, Uri? value, CsvColumnAttribute? attribute, char delimiter) { - if (value?.ToString() is string address) { - stringBuilder - .Append('"') - .Append(address.Replace("\"", "\"\"")) - .Append('"'); - } - } - - public Uri? Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute) { - if (text.Length == 0) { - return null; - } else { - return new Uri(text.ToString()); - } - } - - /// A local of type - [ConverterEmitter(typeof(Uri))] - public void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? _, CsvColumnAttribute? attribute) => gen - .Dup() - .Ldnull() - .Cgt_Un() - .Brfalse_S(out Label @else) - .Stloc(local!) - .Ldc_I4_X((int)'"') - .Callvirt("Append", typeof(char)) - .Ldloc(local!) - .Callvirt("ToString") - .Ldstr("\"") - .Ldstr("\"\"") - .Callvirt("Replace", typeof(string), typeof(string)) - .Callvirt("Append", typeof(string)) - .Ldc_I4_X((int)'"') - .Callvirt("Append", typeof(char)) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Label(@endif); - - [ConverterEmitter] - public void EmitDeserialize(ILGenerator gen, LocalBuilder? _, LocalBuilder? __, CsvColumnAttribute? attribute) => gen - .Dup() - .CallPropertyGet>("Length") - .Brfalse_S(out Label @else) - .Callvirt>("ToString") - .Newobj(typeof(string)) - .Br_S(out Label @endif) - .Label(@else) - .Pop() - .Ldnull() - .Label(@endif); - } -} diff --git a/CsvSerializer/Internal/Helpers/ReadOnlyMemoryHelper.cs b/CsvSerializer/Internal/Helpers/ReadOnlyMemoryHelper.cs deleted file mode 100644 index 43bcc54..0000000 --- a/CsvSerializer/Internal/Helpers/ReadOnlyMemoryHelper.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace Csv.Internal.Helpers { - internal static class ReadOnlyMemoryHelper { - public static int IndexOf(ReadOnlyMemory memory, char c) => memory.Span.IndexOf(c); - } -} diff --git a/CsvSerializer/Internal/Helpers/TypeHelper.cs b/CsvSerializer/Internal/Helpers/TypeHelper.cs deleted file mode 100644 index 85ba32e..0000000 --- a/CsvSerializer/Internal/Helpers/TypeHelper.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Csv.Internal.Helpers { - internal static class TypeHelper { - public static bool IsPublicAndIsNotAnonymous() => - typeof(T).Name is string className - && (className.Length < 15 - || className[0] != '<' - || className[1] != '>') - && typeof(T).IsPublic; - - public static bool IsInternalOrAnonymous() => !IsPublicAndIsNotAnonymous(); - } -} diff --git a/CsvSerializer/Internal/IConverter.cs b/CsvSerializer/Internal/IConverter.cs deleted file mode 100644 index 44d63d0..0000000 --- a/CsvSerializer/Internal/IConverter.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.Text; - -namespace Csv.Internal { - internal interface IConverter { - void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, T value, CsvColumnAttribute? attribute, char delimiter); - T Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute); - } -} diff --git a/CsvSerializer/Internal/IConverterEmitter.cs b/CsvSerializer/Internal/IConverterEmitter.cs deleted file mode 100644 index 8b16673..0000000 --- a/CsvSerializer/Internal/IConverterEmitter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal { - internal interface IConverterEmitter { - /// - /// Value is loaded to the top of stack. Pop it. Under the value is a . Keep it there. - /// arg0 is an . - /// - void EmitAppendToStringBuilder(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute); - - /// - /// Unquoted string literal is loaded as a to top of stack. Pop it then push its deserialized value to stack. - /// arg0 is an IFormatProvider. - /// - void EmitDeserialize(ILGenerator gen, LocalBuilder? local, LocalBuilder? secondaryLocal, CsvColumnAttribute? attribute); - } -} diff --git a/CsvSerializer/Internal/IDeserializer.cs b/CsvSerializer/Internal/IDeserializer.cs deleted file mode 100644 index 882c4eb..0000000 --- a/CsvSerializer/Internal/IDeserializer.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Csv { - internal interface IDeserializer { - List Deserialize(IFormatProvider provider, char delimiter, bool skipHeader, ReadOnlyMemory csv); - } -} diff --git a/CsvSerializer/Internal/INativeConverter.cs b/CsvSerializer/Internal/INativeConverter.cs deleted file mode 100644 index b170cb4..0000000 --- a/CsvSerializer/Internal/INativeConverter.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Csv.Internal { - internal interface INativeConverter : IConverter, IConverterEmitter { } -} diff --git a/CsvSerializer/Internal/ISerializer.cs b/CsvSerializer/Internal/ISerializer.cs deleted file mode 100644 index 3ccb7f2..0000000 --- a/CsvSerializer/Internal/ISerializer.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.Text; - -namespace Csv { - internal interface ISerializer { - void SerializeHeader(char delimiter, StringBuilder stringBuilder); - void SerializeItem(IFormatProvider provider, char delimiter, StringBuilder stringBuilder, object item); - } -} diff --git a/CsvSerializer/Internal/NaiveImpl/NaiveDeserializer.cs b/CsvSerializer/Internal/NaiveImpl/NaiveDeserializer.cs deleted file mode 100644 index b99d523..0000000 --- a/CsvSerializer/Internal/NaiveImpl/NaiveDeserializer.cs +++ /dev/null @@ -1,379 +0,0 @@ -using Csv.Internal; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Reflection; - -namespace Csv.Internal.NaiveImpl { - internal class NaiveDeserializer : IDeserializer where T : notnull { - private enum DeserializeAs { - Boolean, - SByte, - Byte, - Int16, - UInt16, - Int32, - UInt32, - Int64, - UInt64, - Single, - Double, - Decimal, - String, - DateTime, - Uri, - Enum - } - - private readonly PropertyInfo[] _properties; - private readonly CsvColumnAttribute?[] _columnAttributes; - private readonly DeserializeAs[] _deserializeAs; - private readonly bool[] _isNullable; - - public NaiveDeserializer() { - _properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); - _columnAttributes = new CsvColumnAttribute?[_properties.Length]; - _deserializeAs = new DeserializeAs[_properties.Length]; - _isNullable = new bool[_properties.Length]; - for (int i = 0; i < _properties.Length; i++) { - _columnAttributes[i] = _properties[i].GetCustomAttribute(); - switch (_properties[i].PropertyType) { - case Type tSByte when tSByte == typeof(sbyte): - _deserializeAs[i] = DeserializeAs.SByte; - _isNullable[i] = false; - break; - case Type tNullableSByte when Nullable.GetUnderlyingType(tNullableSByte) == typeof(sbyte): - _deserializeAs[i] = DeserializeAs.SByte; - _isNullable[i] = true; - break; - case Type tByte when tByte == typeof(byte): - _deserializeAs[i] = DeserializeAs.Byte; - _isNullable[i] = false; - break; - case Type tNullableByte when Nullable.GetUnderlyingType(tNullableByte) == typeof(byte): - _deserializeAs[i] = DeserializeAs.Byte; - _isNullable[i] = true; - break; - case Type tInt16 when tInt16 == typeof(short): - _deserializeAs[i] = DeserializeAs.Int16; - _isNullable[i] = false; - break; - case Type tNullableInt16 when Nullable.GetUnderlyingType(tNullableInt16) == typeof(short): - _deserializeAs[i] = DeserializeAs.Int16; - _isNullable[i] = true; - break; - case Type tUInt16 when tUInt16 == typeof(ushort): - _deserializeAs[i] = DeserializeAs.UInt16; - _isNullable[i] = false; - break; - case Type tNullableUInt16 when Nullable.GetUnderlyingType(tNullableUInt16) == typeof(ushort): - _deserializeAs[i] = DeserializeAs.UInt16; - _isNullable[i] = true; - break; - case Type tInt32 when tInt32 == typeof(int): - _deserializeAs[i] = DeserializeAs.Int32; - _isNullable[i] = false; - break; - case Type tNullableInt32 when Nullable.GetUnderlyingType(tNullableInt32) == typeof(int): - _deserializeAs[i] = DeserializeAs.Int32; - _isNullable[i] = true; - break; - case Type tUint32 when tUint32 == typeof(uint): - _deserializeAs[i] = DeserializeAs.UInt32; - _isNullable[i] = false; - break; - case Type tNullableUint32 when Nullable.GetUnderlyingType(tNullableUint32) == typeof(uint): - _deserializeAs[i] = DeserializeAs.UInt32; - _isNullable[i] = true; - break; - case Type tInt64 when tInt64 == typeof(long): - _deserializeAs[i] = DeserializeAs.Int64; - _isNullable[i] = false; - break; - case Type tNullableInt64 when Nullable.GetUnderlyingType(tNullableInt64) == typeof(long): - _deserializeAs[i] = DeserializeAs.Int64; - _isNullable[i] = true; - break; - case Type tUInt64 when tUInt64 == typeof(ulong): - _deserializeAs[i] = DeserializeAs.UInt64; - _isNullable[i] = false; - break; - case Type tNullableUInt64 when Nullable.GetUnderlyingType(tNullableUInt64) == typeof(ulong): - _deserializeAs[i] = DeserializeAs.UInt64; - _isNullable[i] = true; - break; - case Type tSingle when tSingle == typeof(float): - _deserializeAs[i] = DeserializeAs.Single; - _isNullable[i] = false; - break; - case Type tNullableSingle when Nullable.GetUnderlyingType(tNullableSingle) == typeof(float): - _deserializeAs[i] = DeserializeAs.Single; - _isNullable[i] = true; - break; - case Type tDouble when tDouble == typeof(double): - _deserializeAs[i] = DeserializeAs.Double; - _isNullable[i] = false; - break; - case Type tNullableDouble when Nullable.GetUnderlyingType(tNullableDouble) == typeof(double): - _deserializeAs[i] = DeserializeAs.Double; - _isNullable[i] = true; - break; - case Type tDecimal when tDecimal == typeof(decimal): - _deserializeAs[i] = DeserializeAs.Decimal; - _isNullable[i] = false; - break; - case Type tNullableDecimal when Nullable.GetUnderlyingType(tNullableDecimal) == typeof(decimal): - _deserializeAs[i] = DeserializeAs.Decimal; - _isNullable[i] = true; - break; - case Type tBoolean when tBoolean == typeof(bool): - _deserializeAs[i] = DeserializeAs.Boolean; - _isNullable[i] = false; - break; - case Type tNullableBoolean when Nullable.GetUnderlyingType(tNullableBoolean) == typeof(bool): - _deserializeAs[i] = DeserializeAs.Boolean; - _isNullable[i] = true; - break; - case Type tString when tString == typeof(string): - _deserializeAs[i] = DeserializeAs.String; - _isNullable[i] = true; - break; - case Type tDateTime when tDateTime == typeof(DateTime): - _deserializeAs[i] = DeserializeAs.DateTime; - _isNullable[i] = false; - break; - case Type tNullableDateTime when Nullable.GetUnderlyingType(tNullableDateTime) == typeof(DateTime): - _deserializeAs[i] = DeserializeAs.DateTime; - _isNullable[i] = true; - break; - case Type tUri when tUri == typeof(Uri): - _deserializeAs[i] = DeserializeAs.Uri; - _isNullable[i] = true; - break; - case Type tEnum when tEnum.IsEnum: - _deserializeAs[i] = DeserializeAs.Enum; - _isNullable[i] = false; - break; - case Type tNullableEnum when Nullable.GetUnderlyingType(tNullableEnum)?.IsEnum == true: - _deserializeAs[i] = DeserializeAs.Enum; - _isNullable[i] = true; - break; - default: - throw new CsvTypeException(_properties[i].PropertyType); - } - } - } - - public List Deserialize(IFormatProvider provider, char delimiter, bool skipHeader, ReadOnlyMemory csv) { - bool firstRow = true; - List items = new List(); - while (csv.Length > 0) { - List> columns = StringSplitter.ReadNextLine(ref csv, delimiter); - if (firstRow && skipHeader) { - firstRow = false; - continue; - } - if (_properties.Length != columns.Count) { - int endOfLine = csv.Span.IndexOf('\n'); - string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString(); - throw new CsvFormatException(typeof(T), line, $"Row must consists of {_properties.Length} columns."); - } - T item = Activator.CreateInstance(); - for (int i = 0; i < _properties.Length; i++) { - switch (_deserializeAs[i]) { - case DeserializeAs.SByte: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && sbyte.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out sbyte vSByte)) { - _properties[i].SetValue(item, vSByte); - } else if (sbyte.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vSByte)) { - _properties[i].SetValue(item, vSByte); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct sbyte format."); - } - break; - case DeserializeAs.Byte: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && byte.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out byte vByte)) { - _properties[i].SetValue(item, vByte); - } else if (byte.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vByte)) { - _properties[i].SetValue(item, vByte); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct byte format."); - } - break; - case DeserializeAs.Int16: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && short.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out short vInt16)) { - _properties[i].SetValue(item, vInt16); - } else if (short.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vInt16)) { - _properties[i].SetValue(item, vInt16); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Int16 format."); - } - break; - case DeserializeAs.UInt16: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && ushort.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out ushort vUInt16)) { - _properties[i].SetValue(item, vUInt16); - } else if (ushort.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vUInt16)) { - _properties[i].SetValue(item, vUInt16); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct UInt16 format."); - } - break; - case DeserializeAs.Int32: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && int.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out int vInt32)) { - _properties[i].SetValue(item, vInt32); - } else if (int.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vInt32)) { - _properties[i].SetValue(item, vInt32); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Int32 format."); - } - break; - case DeserializeAs.UInt32: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && uint.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out uint vUInt32)) { - _properties[i].SetValue(item, vUInt32); - } else if (uint.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vUInt32)) { - _properties[i].SetValue(item, vUInt32); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct UInt32 format."); - } - break; - case DeserializeAs.Int64: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && long.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out long vInt64)) { - _properties[i].SetValue(item, vInt64); - } else if (long.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vInt64)) { - _properties[i].SetValue(item, vInt64); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Int64 format."); - } - break; - case DeserializeAs.UInt64: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && ulong.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out ulong vUInt64)) { - _properties[i].SetValue(item, vUInt64); - } else if (ulong.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vUInt64)) { - _properties[i].SetValue(item, vUInt64); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct UInt64 format."); - } - break; - case DeserializeAs.Single: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && float.TryParse(columns[i].Span[1..^1], NumberStyles.Float, provider, out float vSingle)) { - _properties[i].SetValue(item, vSingle); - } else if (float.TryParse(columns[i].Span, NumberStyles.Float, provider, out vSingle)) { - _properties[i].SetValue(item, vSingle); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct floating point format."); - } - break; - case DeserializeAs.Double: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && double.TryParse(columns[i].Span[1..^1], NumberStyles.Float, provider, out double vDouble)) { - _properties[i].SetValue(item, vDouble); - } else if (double.TryParse(columns[i].Span, NumberStyles.Float, provider, out vDouble)) { - _properties[i].SetValue(item, vDouble); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct floating point format."); - } - break; - case DeserializeAs.Decimal: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && decimal.TryParse(columns[i].Span[1..^1], NumberStyles.Number, provider, out decimal vDecimal)) { - _properties[i].SetValue(item, vDecimal); - } else if (decimal.TryParse(columns[i].Span, NumberStyles.Number, provider, out vDecimal)) { - _properties[i].SetValue(item, vDecimal); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct decimal format."); - } - break; - case DeserializeAs.Boolean: - if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && bool.TryParse(columns[i].Span[1..^1], out bool vBoolean)) { - _properties[i].SetValue(item, vBoolean); - } else if (bool.TryParse(columns[i].Span, out vBoolean)) { - _properties[i].SetValue(item, vBoolean); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Boolean format."); - } - break; - case DeserializeAs.String: - string s = columns[i].ToString().Trim(); -#if NETSTANDARD2_0 - if (s.StartsWith("\"") - && s.EndsWith("\"")) { - s = s.Substring(1, s.Length - 2); - } -#else - if (s.StartsWith('"') - && s.EndsWith('"')) { - s = s[1..^1]; - } -#endif - s = s.Replace("\"\"", "\"").TrimEnd('\r'); - _properties[i].SetValue(item, s); - break; - case DeserializeAs.DateTime: - s = columns[i].ToString().Trim(); -#if NETSTANDARD2_0 - if (s.StartsWith("\"") - && s.EndsWith("\"")) { - s = s.Substring(1, s.Length - 2); - } -#else - if (s.StartsWith('"') - && s.EndsWith('"')) { - s = s[1..^1]; - } -#endif - DateTime vDateTime; - if (_columnAttributes[i]?.DateFormat switch { - string dateFormat => DateTime.TryParseExact(s, dateFormat, null, DateTimeStyles.AssumeLocal, out vDateTime), - _ => DateTime.TryParse(s, null, DateTimeStyles.AssumeLocal, out vDateTime) - }) { - _properties[i].SetValue(item, vDateTime); - } else if (!_isNullable[i] || !string.IsNullOrWhiteSpace(s)) { - if (_columnAttributes[i]?.DateFormat is string dateFormat) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), $"Input string was not in correct DateTime format. Expected format was '{dateFormat}'."); - } else { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct DateTime format."); - } - } else { - _properties[i].SetValue(item, null); - } - break; - case DeserializeAs.Uri: - s = columns[i].ToString().Trim(); -#if NETSTANDARD2_0 - if (s.StartsWith("\"") - && s.EndsWith("\"")) { - s = s.Substring(1, s.Length - 2); - } -#else - if (s.StartsWith('"') - && s.EndsWith('"')) { - s = s[1..^1]; - } -#endif - s = s.Replace("\"\"", "\"").TrimEnd('\r'); - if (string.IsNullOrWhiteSpace(s)) { - _properties[i].SetValue(item, null); - } else { - _properties[i].SetValue(item, new Uri(s)); - } - break; - case DeserializeAs.Enum: - Type enumType; - if (_isNullable[i]) { - enumType = Nullable.GetUnderlyingType(_properties[i].PropertyType)!; - } else { - enumType = _properties[i].PropertyType; - } - if (Enum.TryParse(enumType, columns[i].ToString(), out object? vEnum) && vEnum != null) { - _properties[i].SetValue(item, vEnum); - } else if (!_isNullable[i] || columns[i].Length > 0) { - throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), $"Input string was not in a valid {_properties[i].PropertyType.Name} value."); - } - break; - default: - throw new NotImplementedException(); - } - } - items.Add(item); - } - return items; - } - } -} diff --git a/CsvSerializer/Internal/NaiveImpl/NaiveSerializer.cs b/CsvSerializer/Internal/NaiveImpl/NaiveSerializer.cs deleted file mode 100644 index d9d1ca2..0000000 --- a/CsvSerializer/Internal/NaiveImpl/NaiveSerializer.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Globalization; -using System.Reflection; -using System.Text; - -namespace Csv.Internal.NaiveImpl { - internal class NaiveSerializer : ISerializer where T : notnull { - private enum SerializeAs { - Number, - String, - DateTime, - Uri, - Enum - } - - private readonly PropertyInfo[] _properties; - private readonly CsvColumnAttribute?[] _columnAttributes; - private readonly SerializeAs[] _serializeAs; - - public NaiveSerializer() { - _properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); - _columnAttributes = new CsvColumnAttribute?[_properties.Length]; - _serializeAs = new SerializeAs[_properties.Length]; - for (int i = 0; i < _properties.Length; i++) { - _columnAttributes[i] = _properties[i].GetCustomAttribute(); - switch (_properties[i].PropertyType) { - case Type tSByte when tSByte == typeof(sbyte): - case Type tNullableSByte when Nullable.GetUnderlyingType(tNullableSByte) == typeof(sbyte): - case Type tByte when tByte == typeof(byte): - case Type tNullableByte when Nullable.GetUnderlyingType(tNullableByte) == typeof(byte): - case Type tInt16 when tInt16 == typeof(short): - case Type tNullableInt16 when Nullable.GetUnderlyingType(tNullableInt16) == typeof(short): - case Type tUInt16 when tUInt16 == typeof(ushort): - case Type tNullableUInt16 when Nullable.GetUnderlyingType(tNullableUInt16) == typeof(ushort): - case Type tInt32 when tInt32 == typeof(int): - case Type tNullableInt32 when Nullable.GetUnderlyingType(tNullableInt32) == typeof(int): - case Type tUint32 when tUint32 == typeof(uint): - case Type tNullableUint32 when Nullable.GetUnderlyingType(tNullableUint32) == typeof(uint): - case Type tInt64 when tInt64 == typeof(long): - case Type tNullableInt64 when Nullable.GetUnderlyingType(tNullableInt64) == typeof(long): - case Type tUInt64 when tUInt64 == typeof(ulong): - case Type tNullableUInt64 when Nullable.GetUnderlyingType(tNullableUInt64) == typeof(ulong): - case Type tSingle when tSingle == typeof(float): - case Type tNullableSingle when Nullable.GetUnderlyingType(tNullableSingle) == typeof(float): - case Type tDouble when tDouble == typeof(double): - case Type tNullableDouble when Nullable.GetUnderlyingType(tNullableDouble) == typeof(double): - case Type tDecimal when tDecimal == typeof(decimal): - case Type tNullableDecimal when Nullable.GetUnderlyingType(tNullableDecimal) == typeof(decimal): - case Type tBoolean when tBoolean == typeof(bool): - case Type tNullableBoolean when Nullable.GetUnderlyingType(tNullableBoolean) == typeof(bool): - _serializeAs[i] = SerializeAs.Number; - break; - case Type tString when tString == typeof(string): - _serializeAs[i] = SerializeAs.String; - break; - case Type tDateTime when tDateTime == typeof(DateTime): - case Type tNullableDateTime when Nullable.GetUnderlyingType(tNullableDateTime) == typeof(DateTime): - _serializeAs[i] = SerializeAs.DateTime; - break; - case Type tUri when tUri == typeof(Uri): - _serializeAs[i] = SerializeAs.Uri; - break; - case Type tEnum when tEnum.IsEnum: - _serializeAs[i] = SerializeAs.Enum; - break; - case Type tNullableEnum when Nullable.GetUnderlyingType(tNullableEnum)?.IsEnum == true: - _serializeAs[i] = SerializeAs.Enum; - break; - default: - throw new CsvTypeException(_properties[i].PropertyType); - } - } - } - - public void SerializeHeader(char delimiter, StringBuilder stringBuilder) { - bool firstProperty = true; - for (int i = 0; i < _properties.Length; i++) { - if (!firstProperty) { - stringBuilder.Append(delimiter); - } - stringBuilder.Append('"'); - stringBuilder.Append((_columnAttributes[i]?.Name ?? _properties[i].Name).Replace("\"", "\"\"")); - stringBuilder.Append('"'); - firstProperty = false; - } - stringBuilder.Append("\r\n"); - } - - public void SerializeItem(IFormatProvider provider, char delimiter, StringBuilder stringBuilder, object item) { - bool firstProperty = true; - for (int i = 0; i < _properties.Length; i++) { - if (!firstProperty) { - stringBuilder.Append(delimiter); - } - switch (_serializeAs[i]) { - case SerializeAs.Number: - string? str = Convert.ToString(_properties[i].GetValue(item), provider); - if (str is string && str.Contains(delimiter)) { - stringBuilder.AppendFormat("\"{0}\"", str); - } else { - stringBuilder.AppendFormat("{0}", str); - } - break; - case SerializeAs.String: - if (((string?)_properties[i].GetValue(item))?.Replace("\"", "\"\"") is string stringValue) { - stringBuilder.Append('"'); - stringBuilder.Append(stringValue); - stringBuilder.Append('"'); - } - break; - case SerializeAs.DateTime: - if (((DateTime?)_properties[i].GetValue(item)) is DateTime dateTimeValue) { - stringBuilder.Append('"'); - if (_columnAttributes[i]?.DateFormat is string dateFormat) { - stringBuilder.Append(dateTimeValue.ToString(dateFormat, provider)); - } else { - stringBuilder.Append(dateTimeValue.ToString(provider)); - } - stringBuilder.Append('"'); - } - break; - case SerializeAs.Uri: - if (((Uri?)_properties[i].GetValue(item)) is Uri uri && uri.ToString().Replace("\"", "\"\"") is string uriString) { - stringBuilder.Append('"'); - stringBuilder.Append(uriString); - stringBuilder.Append('"'); - } - break; - case SerializeAs.Enum: - stringBuilder.AppendFormat("{0}", _properties[i].GetValue(item)); - break; - default: - throw new NotImplementedException(); - } - firstProperty = false; - } - stringBuilder.Append("\r\n"); - } - } -} diff --git a/CsvSerializer/Internal/NativeImpl/DeserializerFactory.cs b/CsvSerializer/Internal/NativeImpl/DeserializerFactory.cs deleted file mode 100644 index bc5b99e..0000000 --- a/CsvSerializer/Internal/NativeImpl/DeserializerFactory.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Csv.Internal.Helpers; -using Csv.Internal.NaiveImpl; -using Missil; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; - -namespace Csv.Internal.NativeImpl { - internal static class DeserializerFactory { - private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - - public static IDeserializer GetOrCreate() where T : notnull { - if (_cache.TryGetValue(typeof(T), out IDeserializer? deserializer)) return deserializer; - //if (TypeHelper.IsInternalOrAnonymous()) { - deserializer = new NaiveDeserializer(); - //} else { - // ImplEmitter implEmitter = new ImplEmitter($"D{typeof(T).GUID.ToString("N")}"); - // implEmitter.ImplementFunc, IFormatProvider, char, bool, ReadOnlyMemory>("Deserialize", gen => DefineDeserialize(gen)); - // deserializer = implEmitter.CreateInstance(); - //} - _cache.TryAdd(typeof(T), deserializer); - return deserializer; - } - - private static void DefineDeserialize(ILGenerator gen) { - PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); - Dictionary localByType = new Dictionary(); - ConstructorInfo? constructorInfo = typeof(T).GetConstructor(Type.EmptyTypes); - bool isDefaultConstructor; - if (constructorInfo != null) { - isDefaultConstructor = true; - } else { - Type[] expectedConstructorSignature = properties.Select(prop => prop.PropertyType).ToArray(); - constructorInfo = typeof(T).GetConstructor(expectedConstructorSignature) ?? throw new CsvTypeException(typeof(T), "No suitable constructor found."); - isDefaultConstructor = false; - } - gen - .DeclareLocal(out LocalBuilder firstRow) - .DeclareLocal>(out LocalBuilder items) - .DeclareLocal>>(out LocalBuilder columns) - .DeclareLocal(out LocalBuilder endOfLine) - .DeclareLocal(out LocalBuilder excMessage) - .DeclareLocal(out LocalBuilder obj) - .DeclareLocal>(out LocalBuilder col) - .Ldc_I4_1() - .Stloc(firstRow) - .Newobj(typeof(List).GetConstructor(Type.EmptyTypes)!) - .Stloc(items) - .Label(out Label beginLoop) - .Ldarg(4); // csv - gen.CallvirtPropertyGet>("Length") - .Brfalse(out Label endLoop) - .Ldarga_S(4) // csv - .Ldarg_2(); // delimiter - gen.Call(typeof(StringSplitter).GetMethod("ReadNextLine", new Type[] { typeof(ReadOnlyMemory).MakeByRefType(), typeof(char) })!) - .Stloc(columns) - .Ldarg_3() // skipHeader - .Brfalse_S(out Label dontSkipHeader) - .Ldloc(firstRow) - .Brfalse_S(dontSkipHeader) - .Ldnull() - .Stloc(firstRow) - .Br_S(beginLoop) - .Label(dontSkipHeader) - .Ldc_I4_X(properties.Length) - .Ldloc(columns); - gen.CallvirtPropertyGet>>("Count") - .Ceq() - .Brtrue_S(out Label validated) - .Ldarg(4) - .Ldc_I4_S((byte)'\n'); - gen.Call(typeof(ReadOnlyMemoryHelper).GetMethod("IndexOf", new Type[] { typeof(ReadOnlyMemory), typeof(char) })!) - .Stloc(endOfLine) - .Ldloc(endOfLine) - .Ldc_I4_M1() - .Ceq() - .Brfalse_S(out Label splitMemory) - .Ldarg(4); - gen.Callvirt>("ToString") - .Stloc(excMessage) - .Br_S(out Label throwExc) - .Label(splitMemory) - .Ldarg(4) - .Ldc_I4_0() - .Ldloc(endOfLine); - gen.Call>("Slice", typeof(int), typeof(int)); - gen.Callvirt>("ToString") - .Stloc(excMessage) - .Label(throwExc) - .Ldtoken() - .Ldloc(excMessage) - .Ldstr($"Row must consists of {properties.Length} columns.") - .Initobj() - .Throw() - .Label(validated) - .Emit(gen => { - if (isDefaultConstructor) { - gen - .Newobj(constructorInfo) - .Stloc(obj) - .Ldloc(obj); - } - }) - .Emit(gen => { - for (int i = 0; i < properties.Length; i++) { - Type propertyType = properties[i].PropertyType; - CsvColumnAttribute? columnAttribute = properties[i].GetCustomAttribute(); - IConverterEmitter converterEmitter = ConverterFactory.GetOrCreateEmitter(propertyType); - ConverterEmitterAttribute converterEmitterAttribute = converterEmitter.GetType().GetMethod("EmitDeserialize")!.GetCustomAttribute()!; - LocalBuilder? local = null; - LocalBuilder? secondaryLocal = null; - if (converterEmitterAttribute.PrimaryLocalType is Type localType) { - if (!localByType.TryGetValue(localType, out local)) { - local = gen.DeclareLocal(localType); - localByType.Add(localType, local); - } - } - if (converterEmitterAttribute.SecondaryLocalType is Type secondaryLocalType) { - if (!localByType.TryGetValue(secondaryLocalType, out secondaryLocal)) { - secondaryLocal = gen.DeclareLocal(secondaryLocalType); - localByType.Add(secondaryLocalType, secondaryLocal); - } - } - gen - .Emit(gen => { - if (isDefaultConstructor) { - gen.Ldloc(obj); - } - }) - .Ldloc(columns) - .Ldc_I4_X(i); - gen.Callvirt(typeof(List>).GetProperties(BindingFlags.Public | BindingFlags.Instance).Single(prop => prop.GetIndexParameters().Length > 0).GetGetMethod()!) - .Stloc(col) - .Ldloc(col) - .Emit(gen => converterEmitter.EmitDeserialize(gen, local, secondaryLocal, columnAttribute)) - .Emit(gen => { - if (isDefaultConstructor) { - gen - .Callvirt(properties[i].GetSetMethod()!); - } - }); - } - }) - .Emit(gen => { - if (!isDefaultConstructor) { - gen - .Newobj(constructorInfo); - } - }) - .Stloc(obj) - .Ldloc(items) - .Ldloc(obj); - gen.Callvirt>("Add", typeof(object)) - .Br(beginLoop) - .Label(endLoop) - .Ldloc(items) - .Ret(); - } - } -} diff --git a/CsvSerializer/Internal/NativeImpl/ImplEmitter.cs b/CsvSerializer/Internal/NativeImpl/ImplEmitter.cs deleted file mode 100644 index 7c5b600..0000000 --- a/CsvSerializer/Internal/NativeImpl/ImplEmitter.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.Reflection; -using System.Reflection.Emit; - -namespace Csv.Internal.NativeImpl { - internal class ImplEmitter where TInterface : class { - private readonly TypeBuilder _typeBuilder; - private Type? _type; - - public ImplEmitter(string className) { - AssemblyName assemblyName = new AssemblyName("CsvSerializer.Dynamic"); - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect); - ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("CsvSerializer.DynamicModule"); - _typeBuilder = moduleBuilder.DefineType( - name: className, - attr: TypeAttributes.Public | - TypeAttributes.Class | - TypeAttributes.AutoClass | - TypeAttributes.AnsiClass | - TypeAttributes.BeforeFieldInit | - TypeAttributes.AutoLayout, - parent: null, - interfaces: new Type[] { typeof(TInterface) }); - } - - public void ImplementAction(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(void), - new Type[] { }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { })!); - } - - public void ImplementAction(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(void), - new Type[] { typeof(T1) }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { typeof(T1) })!); - } - - public void ImplementAction(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(void), - new Type[] { typeof(T1), typeof(T2) }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { typeof(T1), typeof(T2) })!); - } - - public void ImplementAction(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(void), - new Type[] { typeof(T1), typeof(T2), typeof(T3) }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { typeof(T1), typeof(T2), typeof(T3) })!); - } - - public void ImplementAction(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(void), - new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) })!); - } - - public void ImplementFunc(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(TReturn), - new Type[] { }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { })!); - } - - public void ImplementFunc(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(TReturn), - new Type[] { typeof(T1) }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { typeof(T1) })!); - } - - public void ImplementFunc(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(TReturn), - new Type[] { typeof(T1), typeof(T2) }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { typeof(T1), typeof(T2) })!); - } - - public void ImplementFunc(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(TReturn), - new Type[] { typeof(T1), typeof(T2), typeof(T3) }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { typeof(T1), typeof(T2), typeof(T3) })!); - } - - public void ImplementFunc(string methodName, Action ilGenerator) { - if (_type != null) { - throw new InvalidOperationException("Type already created."); - } - MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, - MethodAttributes.Public | - MethodAttributes.Virtual, - typeof(TReturn), - new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }); - ILGenerator gen = methodBuilder.GetILGenerator(); - ilGenerator.Invoke(gen); - _typeBuilder.DefineMethodOverride(methodBuilder, typeof(TInterface).GetMethod(methodName, new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) })!); - } - - public TInterface CreateInstance() { - if (_type == null) { - _type = _typeBuilder.CreateType()!; - } - return (TInterface)Activator.CreateInstance(_type)!; - } - } -} diff --git a/CsvSerializer/Internal/NativeImpl/SerializerFactory.cs b/CsvSerializer/Internal/NativeImpl/SerializerFactory.cs deleted file mode 100644 index afd67e7..0000000 --- a/CsvSerializer/Internal/NativeImpl/SerializerFactory.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Csv.Internal.Helpers; -using Csv.Internal.NaiveImpl; -using Missil; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; - -namespace Csv.Internal.NativeImpl { - internal static class SerializerFactory { - private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - - public static ISerializer GetOrCreate() where T : notnull { - if (_cache.TryGetValue(typeof(T), out ISerializer? serializer)) return serializer; - //if (TypeHelper.IsInternalOrAnonymous()) { - serializer = new NaiveSerializer(); - //} else { - // ImplEmitter implEmitter = new ImplEmitter($"S{typeof(T).GUID.ToString("N")}"); - // implEmitter.ImplementAction("SerializeHeader", gen => DefineSerializeHeader(gen)); - // implEmitter.ImplementAction("SerializeItem", gen => DefineSerializeItem(gen)); - // serializer = implEmitter.CreateInstance(); - //} - _cache.TryAdd(typeof(T), serializer); - return serializer; - } - - private static void DefineSerializeHeader(ILGenerator gen) { - gen - .Ldarg_2() - .Emit(gen => { - bool firstProperty = true; - foreach (PropertyInfo property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { - CsvColumnAttribute? columnAttribute = property.GetCustomAttribute(); - if (!firstProperty) { - gen - .Ldarg_1() - .Callvirt("Append", typeof(char)); - } - gen - .Ldstr("\"" + (columnAttribute?.Name ?? property.Name).Replace("\"", "\"\"") + "\"") - .Callvirt("Append", typeof(string)); - firstProperty = false; - } - }) - .Ldstr("\r\n") - .Callvirt("Append", typeof(string)) - .Pop() - .Ret(); - } - - private static void DefineSerializeItem(ILGenerator gen) { - PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); - Dictionary localByType = new Dictionary(); - - gen - .Ldarg_3() - .Emit(gen => { - bool firstProperty = true; - foreach (PropertyInfo property in properties) { - if (!property.CanRead) throw new CsvTypeException(typeof(T), property.Name, "Property doesn't have a public getter."); - CsvColumnAttribute? columnAttribute = property.GetCustomAttribute(); - gen - .Emit(gen => firstProperty switch { - false => gen - .Ldarg_2() - .Callvirt("Append", typeof(char)), - _ => gen - }) - .Emit(gen => gen - .Ldarg(4) - .Callvirt(property.GetGetMethod()!) - .Emit(gen => { - IConverterEmitter converterEmitter = ConverterFactory.GetOrCreateEmitter(property.PropertyType); - ConverterEmitterAttribute converterEmitterAttribute = converterEmitter.GetType().GetMethod("EmitAppendToStringBuilder")!.GetCustomAttribute()!; - LocalBuilder? local = null; - LocalBuilder? secondaryLocal = null; - if (converterEmitterAttribute.NullableOfGenericParameterIsPrimaryLocalType && converterEmitter.GetType().IsGenericType) { - Type genericParameter = converterEmitter.GetType().GetGenericArguments()[0]; - Type localType = typeof(Nullable<>).MakeGenericType(genericParameter); - if (!localByType.TryGetValue(localType, out local)) { - local = gen.DeclareLocal(localType); - localByType.Add(localType, local); - } - } else if (converterEmitterAttribute.GenericParameterIsPrimaryLocalType && converterEmitter.GetType().IsGenericType) { - Type localType = converterEmitter.GetType().GetGenericArguments()[0]; - if (!localByType.TryGetValue(localType, out local)) { - local = gen.DeclareLocal(localType); - localByType.Add(localType, local); - } - } else if (converterEmitterAttribute.PrimaryLocalType is Type localType) { - if (!localByType.TryGetValue(localType, out local)) { - local = gen.DeclareLocal(localType); - localByType.Add(localType, local); - } - } - if (converterEmitterAttribute.SecondaryLocalType is Type secondaryLocalType) { - if (!localByType.TryGetValue(secondaryLocalType, out secondaryLocal)) { - secondaryLocal = gen.DeclareLocal(secondaryLocalType); - localByType.Add(secondaryLocalType, secondaryLocal); - } - } - converterEmitter.EmitAppendToStringBuilder(gen, local, secondaryLocal, columnAttribute); - }) - ); - firstProperty = false; - } - }) - .Ldstr("\r\n") - .Callvirt("Append", typeof(string)) - .Pop() - .Ret(); - } - } -} diff --git a/CsvSerializer/Internal/StringSplitter.cs b/CsvSerializer/Internal/StringSplitter.cs deleted file mode 100644 index 24c853e..0000000 --- a/CsvSerializer/Internal/StringSplitter.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Csv.Internal { - internal static class StringSplitter { - private enum ParserState { - InStartingWhiteSpace, - InUnquotedValue, - InQuotedValue, - InEscapeSequence, - InTrailingWhiteSpace - } - - public static List> ReadNextLine(ref ReadOnlyMemory csv, char separator = ',') { - ReadOnlySpan span = csv.Span; - List> columns = new List>(); - int startOfLiteral = 0; - int endOfLiteral = 0; - ParserState state = ParserState.InStartingWhiteSpace; - for (int i = 0, length = csv.Length; i <= length; i++) { - if (i == length) { - switch (state) { - case ParserState.InStartingWhiteSpace: - case ParserState.InUnquotedValue: - case ParserState.InEscapeSequence: - columns.Add(csv.Slice(startOfLiteral, i - startOfLiteral)); - csv = csv.Slice(csv.Length - 1, 0); - return columns; - case ParserState.InQuotedValue: - throw new CsvFormatException(csv.ToString(), "End of file in quoted literal."); - case ParserState.InTrailingWhiteSpace: - columns.Add(csv.Slice(startOfLiteral, endOfLiteral - startOfLiteral + 1)); - csv = csv.Slice(csv.Length - 1, 0); - return columns; - } - } else { - switch (span[i]) { - case '"': - switch (state) { - case ParserState.InStartingWhiteSpace: - startOfLiteral = i; - state = ParserState.InQuotedValue; - break; - case ParserState.InUnquotedValue: - int endOfLine = span.IndexOf('\n'); - string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString(); - throw new CsvFormatException(line, $"Invalid character at position {i}: \""); - case ParserState.InQuotedValue: - state = ParserState.InEscapeSequence; - break; - case ParserState.InEscapeSequence: - state = ParserState.InQuotedValue; - break; - case ParserState.InTrailingWhiteSpace: - endOfLine = span.IndexOf('\n'); - line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString(); - throw new CsvFormatException(line, $"Invalid character at position {i}: \""); - } - break; - case char c when c == separator: - switch (state) { - case ParserState.InStartingWhiteSpace: - case ParserState.InUnquotedValue: - case ParserState.InEscapeSequence: - columns.Add(csv.Slice(startOfLiteral, i - startOfLiteral)); - startOfLiteral = i + 1; - state = ParserState.InStartingWhiteSpace; - break; - case ParserState.InTrailingWhiteSpace: - columns.Add(csv.Slice(startOfLiteral, endOfLiteral - startOfLiteral + 1)); - startOfLiteral = i + 1; - state = ParserState.InStartingWhiteSpace; - break; - } - break; - case '\n': - switch (state) { - case ParserState.InStartingWhiteSpace: - case ParserState.InUnquotedValue: - case ParserState.InEscapeSequence: - columns.Add(csv.Slice(startOfLiteral, i - startOfLiteral)); - csv = csv.Slice(i + 1); - return columns; - case ParserState.InTrailingWhiteSpace: - columns.Add(csv.Slice(startOfLiteral, endOfLiteral - startOfLiteral + 1)); - csv = csv.Slice(i + 1); - return columns; - } - break; - case char c: - switch (state) { - case ParserState.InStartingWhiteSpace: - state = ParserState.InUnquotedValue; - break; - case ParserState.InEscapeSequence: - endOfLiteral = i - 1; - state = ParserState.InTrailingWhiteSpace; - break; - case ParserState.InTrailingWhiteSpace: - if (!char.IsWhiteSpace(c)) { - int endOfLine = span.IndexOf('\n'); - string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString(); - throw new CsvFormatException(line, $"Invalid character at position {i}: {c}"); - } - break; - } - break; - } - } - } - throw new InvalidOperationException("Parser internal error."); - } - - public static List OldSplitLine(string line, char separator = ',') { - List columns = new List(); - int i = 0; - int startOfLiteral; - int endOfLiteral; - - EXPECTING_TOKEN: - if (i == line.Length) { - columns.Add(string.Empty); - return columns; - } - switch (line[i]) { - case ' ': - i++; - goto EXPECTING_TOKEN; - case '"': - goto IN_STRING_LITERAL; - case char c: - if (c == separator) { - columns.Add(string.Empty); - i++; - goto EXPECTING_TOKEN; - } else if (char.IsLetterOrDigit(c) || c == '.' || c == '-' || c == '+') { - goto IN_VALUE_LITERAL; - } else { - throw new CsvFormatException(line, $"Invalid character at position {i}: {c}"); - } - } - - IN_STRING_LITERAL: - { - startOfLiteral = i++; - bool maybeInEscapeCharacter = false; - IN_STRING_LITERAL_LOOP: - if (i == line.Length) { - if (maybeInEscapeCharacter) { - columns.Add(line[startOfLiteral..i]); - return columns; - } else { - throw new CsvFormatException(line, "Newline in string literal."); - } - } - if (maybeInEscapeCharacter) { - maybeInEscapeCharacter = false; - if (line[i] != '"') { - endOfLiteral = i; - goto HAS_LITERAL_LOOP; - } - } else if (line[i] == '"') { - maybeInEscapeCharacter = true; - } - i++; - goto IN_STRING_LITERAL_LOOP; - } - - IN_VALUE_LITERAL: - { - startOfLiteral = i++; - IN_VALUE_LITERAL_LOOP: - if (i == line.Length) { - columns.Add(string.Empty); - return columns; - } - switch (line[i]) { - case ' ': - endOfLiteral = i++; - goto HAS_LITERAL_LOOP; - case char c: - if (c == separator) { - columns.Add(line[startOfLiteral..i]); - i++; - goto EXPECTING_TOKEN; - } else if (char.IsLetterOrDigit(c) || c == '.' || c == '-' || c == '+') { - i++; - goto IN_VALUE_LITERAL_LOOP; - } else { - throw new CsvFormatException(line, $"Invalid character at position {i}: {c}"); - } - } - } - - HAS_LITERAL_LOOP: - if (i == line.Length) { - columns.Add(line[startOfLiteral..endOfLiteral]); - return columns; - } - switch (line[i]) { - case ' ': - i++; - goto HAS_LITERAL_LOOP; - case char c: - if (c == separator) { - columns.Add(line[startOfLiteral..endOfLiteral]); - i++; - goto EXPECTING_TOKEN; - } else { - throw new CsvFormatException(line, $"Invalid character at position {i}: {c}"); - } - } - } - } -} diff --git a/CsvSerializer/Internals/DiagnosticReportedException.cs b/CsvSerializer/Internals/DiagnosticReportedException.cs new file mode 100644 index 0000000..03160e2 --- /dev/null +++ b/CsvSerializer/Internals/DiagnosticReportedException.cs @@ -0,0 +1,11 @@ +using System; +using System.Runtime.Serialization; + +namespace Csv.Internals { + internal class DiagnosticReportedException : Exception { + public DiagnosticReportedException() { } + public DiagnosticReportedException(string message) : base(message) { } + public DiagnosticReportedException(string message, Exception innerException) : base(message, innerException) { } + protected DiagnosticReportedException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/CsvSerializer/Internals/NativeImplGenerator.cs b/CsvSerializer/Internals/NativeImplGenerator.cs new file mode 100644 index 0000000..1d41e55 --- /dev/null +++ b/CsvSerializer/Internals/NativeImplGenerator.cs @@ -0,0 +1,1353 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace Csv.Internals { + internal static class NativeImplGenerator { + public static void AddNativeImplementations(this GeneratorExecutionContext context) { + if (context.SyntaxContextReceiver is not SerializeOrDeserializeSyntaxReceiver { + InvocationLocationsByTypeSymbol: { } invocationLocationsByTypeSymbol + }) { + return; + } + + StringBuilder nativeImplRegistration = new(); + + foreach (KeyValuePair> kvp in invocationLocationsByTypeSymbol) { + try { + // Add native implementations for typeSymbol + (string fullTypeName, string serializerName, string deserializerName) = context.AddNativeImplementations( + typeSymbol: kvp.Key, + invocationLocations: kvp.Value + ); + + // Add native implementations registrations + nativeImplRegistration.Append($$""" + SERIALIZER_BY_TYPE.Add(typeof({{fullTypeName}}), new Csv.Internal.NativeImpl.{{serializerName}}()); + DESERIALIZER_BY_TYPE.Add(typeof({{fullTypeName}}), new Csv.Internal.NativeImpl.{{deserializerName}}()); + + """ + ); + } catch (DiagnosticReportedException) { + continue; + } + } + + context.AddSource( + hintName: "SerializerFactory.g.cs", + sourceText: SourceText.From( + text: $$""" + #nullable enable + using System; + using System.Collections.Generic; + using Csv.Internal.NaiveImpl; + + namespace Csv { + internal static class SerializerFactory { + private static readonly Dictionary SERIALIZER_BY_TYPE = new(); + private static readonly Dictionary DESERIALIZER_BY_TYPE = new(); + + static SerializerFactory() { + {{nativeImplRegistration.ToString().Trim()}} + } + + public static ISerializer GetOrCreateSerializer() where T : notnull { + if (SERIALIZER_BY_TYPE.TryGetValue(typeof(T), out ISerializer? serializer)) return serializer; + serializer = new NaiveSerializer(); + SERIALIZER_BY_TYPE.Add(typeof(T), serializer); + return serializer; + } + + public static IDeserializer GetOrCreateDeserializer() where T : notnull { + if (DESERIALIZER_BY_TYPE.TryGetValue(typeof(T), out IDeserializer? deserializer)) return deserializer; + deserializer = new NaiveDeserializer(); + DESERIALIZER_BY_TYPE.Add(typeof(T), deserializer); + return deserializer; + } + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + } + + private static ( + string FullTypeName, + string SerializerName, + string DeserializerName + ) AddNativeImplementations( + this GeneratorExecutionContext context, + ITypeSymbol typeSymbol, + List invocationLocations + ) { + string fullTypeName = GetFullName(typeSymbol); + string serializerName = $"{fullTypeName.Replace('.', '_')}Serializer"; + string deserializerName = $"{fullTypeName.Replace('.', '_')}Deserializer"; + + List propertySymbols = typeSymbol.GetMembers() + .OfType() + .Where(prop => prop.DeclaredAccessibility == Accessibility.Public && !prop.IsStatic) + .ToList(); + + StringBuilder serializeHeaderBuilder = new(); + StringBuilder serializeItemWithProviderBuilder = new(); + StringBuilder serializeItemWithoutProviderBuilder = new(); + StringBuilder deserializeWithProviderBuilder = new(); + StringBuilder deserializeWithoutProviderBuilder = new(); + + int col = 0; + bool strDeclared = false; + foreach (IPropertySymbol propertySymbol in propertySymbols) { + // Delimiters + if (col > 0) { + serializeHeaderBuilder.AppendLine(""" + stringBuilder.Append(delimiter); + """); + serializeItemWithProviderBuilder.AppendLine(""" + stringBuilder.Append(delimiter); + """); + serializeItemWithoutProviderBuilder.AppendLine(""" + stringBuilder.Append(delimiter); + """); + } + + // Header serializers + string? columnName = propertySymbol.GetAttributes() + .Where(attr => attr.AttributeClass?.Name == "CsvColumnAttribute" && attr.ConstructorArguments.Length >= 1) + .Select(attr => attr.ConstructorArguments[0]) + .Where(arg => arg.Kind == TypedConstantKind.Primitive) + .Select(arg => arg.Value) + .OfType() + .FirstOrDefault(); + string? dateFormat = propertySymbol.GetAttributes() + .Where(attr => attr.AttributeClass?.Name == "CsvColumnAttribute") + .SelectMany(attr => attr.NamedArguments.Where(arg => arg.Key == "DateFormat").Select(arg => arg.Value)) + .Where(arg => arg.Kind == TypedConstantKind.Primitive) + .Select(arg => arg.Value) + .OfType() + .FirstOrDefault(); + + columnName ??= propertySymbol.Name ?? ""; + + serializeHeaderBuilder.AppendLine($$""" + stringBuilder.Append('"').Append("{{columnName}}").Append('"'); + """); + + // Item serializers + ITypeSymbol propertyTypeSymbol = propertySymbol.Type; + switch (propertyTypeSymbol) { + case { SpecialType: SpecialType.System_SByte }: + case { SpecialType: SpecialType.System_Byte }: + case { SpecialType: SpecialType.System_Int16 }: + case { SpecialType: SpecialType.System_UInt16 }: + case { SpecialType: SpecialType.System_Int32 }: + case { SpecialType: SpecialType.System_UInt32 }: + case { SpecialType: SpecialType.System_Int64 }: + case { SpecialType: SpecialType.System_UInt64 }: + if (!strDeclared) { + serializeItemWithProviderBuilder.AppendLine(""" + string? str; + """); + serializeItemWithoutProviderBuilder.AppendLine(""" + string? str; + """); + strDeclared = true; + } + serializeItemWithProviderBuilder.AppendLine($$""" + str = i.{{propertySymbol.Name}}.ToString(provider); + stringBuilder.Append(str); + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + str = i.{{propertySymbol.Name}}.ToString(); + stringBuilder.Append(str); + """); + break; + case { SpecialType: SpecialType.System_Single }: + case { SpecialType: SpecialType.System_Double }: + case { SpecialType: SpecialType.System_Decimal }: + if (!strDeclared) { + serializeItemWithProviderBuilder.AppendLine(""" + string? str; + """); + serializeItemWithoutProviderBuilder.AppendLine(""" + string? str; + """); + strDeclared = true; + } + serializeItemWithProviderBuilder.AppendLine($$""" + str = i.{{propertySymbol.Name}}.ToString(provider); + if (str.Contains(delimiter)) { + stringBuilder.Append('"').Append(str).Append('"'); + } else { + stringBuilder.Append(str); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + str = i.{{propertySymbol.Name}}.ToString(); + if (str.Contains(delimiter)) { + stringBuilder.Append('"').Append(str).Append('"'); + } else { + stringBuilder.Append(str); + } + """); + break; + case { SpecialType: SpecialType.System_Boolean }: + serializeItemWithProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}}) { + stringBuilder.Append("True"); + } else { + stringBuilder.Append("False"); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}}) { + stringBuilder.Append("True"); + } else { + stringBuilder.Append("False"); + } + """); + break; + case { SpecialType: SpecialType.System_String }: + serializeItemWithProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}} is not null) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}} is not null) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + break; + case { TypeKind: TypeKind.Enum }: + serializeItemWithProviderBuilder.AppendLine($$""" + stringBuilder.Append(i.{{propertySymbol.Name}}.ToString()); + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + stringBuilder.Append(i.{{propertySymbol.Name}}.ToString()); + """); + break; + case { SpecialType: SpecialType.System_DateTime }: + if (dateFormat is not null) { + serializeItemWithProviderBuilder.AppendLine($$""" + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.ToString("{{dateFormat.Replace("\\", "\\\\")}}", provider).Replace("\"", "\"\"")); + stringBuilder.Append('"'); + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.ToString("{{dateFormat.Replace("\\", "\\\\")}}").Replace("\"", "\"\"")); + stringBuilder.Append('"'); + """); + } else { + serializeItemWithProviderBuilder.AppendLine($$""" + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.ToString(provider).Replace("\"", "\"\"")); + stringBuilder.Append('"'); + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.ToString().Replace("\"", "\"\"")); + stringBuilder.Append('"'); + """); + } + break; + case { Name: "Uri", ContainingNamespace.Name: "System" }: + serializeItemWithProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}} is not null) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.ToString().Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}} is not null) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.ToString().Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + break; + case { IsValueType: true, NullableAnnotation: NullableAnnotation.Annotated }: + if (propertyTypeSymbol is not INamedTypeSymbol { TypeArguments: { Length: 1 } typeArguments }) { + break; + } + ITypeSymbol underlyingTypeSymbol = typeArguments[0]; + switch (underlyingTypeSymbol) { + case { SpecialType: SpecialType.System_SByte }: + case { SpecialType: SpecialType.System_Byte }: + case { SpecialType: SpecialType.System_Int16 }: + case { SpecialType: SpecialType.System_UInt16 }: + case { SpecialType: SpecialType.System_Int32 }: + case { SpecialType: SpecialType.System_UInt32 }: + case { SpecialType: SpecialType.System_Int64 }: + case { SpecialType: SpecialType.System_UInt64 }: + if (!strDeclared) { + serializeItemWithProviderBuilder.AppendLine(""" + string? str; + """); + serializeItemWithoutProviderBuilder.AppendLine(""" + string? str; + """); + strDeclared = true; + } + serializeItemWithProviderBuilder.AppendLine($$""" + str = i.{{propertySymbol.Name}}?.ToString(provider); + if (str is not null) { + stringBuilder.Append(str); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + str = i.{{propertySymbol.Name}}?.ToString(); + if (str is not null) { + stringBuilder.Append(str); + } + """); + break; + case { SpecialType: SpecialType.System_Single }: + case { SpecialType: SpecialType.System_Double }: + case { SpecialType: SpecialType.System_Decimal }: + if (!strDeclared) { + serializeItemWithProviderBuilder.AppendLine(""" + string? str; + """); + serializeItemWithoutProviderBuilder.AppendLine(""" + string? str; + """); + strDeclared = true; + } + serializeItemWithProviderBuilder.AppendLine($$""" + str = i.{{propertySymbol.Name}}?.ToString(provider); + if (str is not null) { + if (str.Contains(delimiter)) { + stringBuilder.Append('"').Append(str).Append('"'); + } else { + stringBuilder.Append(str); + } + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + str = i.{{propertySymbol.Name}}?.ToString(); + if (str is not null) { + if (str.Contains(delimiter)) { + stringBuilder.Append('"').Append(str).Append('"'); + } else { + stringBuilder.Append(str); + } + } + """); + break; + case { SpecialType: SpecialType.System_Boolean }: + serializeItemWithProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}} == true) { + stringBuilder.Append("True"); + } else if (i.{{propertySymbol.Name}} == false) { + stringBuilder.Append("False"); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}} == true) { + stringBuilder.Append("True"); + } else if (i.{{propertySymbol.Name}} == false) { + stringBuilder.Append("False"); + } + """); + break; + case { SpecialType: SpecialType.System_String }: + serializeItemWithProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}} is not null) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}} is not null) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + break; + case { TypeKind: TypeKind.Enum }: + serializeItemWithProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}}.HasValue) { + stringBuilder.Append(i.{{propertySymbol.Name}}.Value.ToString()); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}}.HasValue) { + stringBuilder.Append(i.{{propertySymbol.Name}}.Value.ToString()); + } + """); + break; + case { SpecialType: SpecialType.System_DateTime }: + if (dateFormat is not null) { + serializeItemWithProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}}.HasValue) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.Value.ToString("{{dateFormat.Replace("\\", "\\\\")}}", provider).Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}}.HasValue) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.Value.ToString("{{dateFormat.Replace("\\", "\\\\")}}").Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + } else { + serializeItemWithProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}}.HasValue) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.Value.ToString(provider).Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + serializeItemWithoutProviderBuilder.AppendLine($$""" + if (i.{{propertySymbol.Name}}.HasValue) { + stringBuilder.Append('"'); + stringBuilder.Append(i.{{propertySymbol.Name}}.Value.ToString().Replace("\"", "\"\"")); + stringBuilder.Append('"'); + } + """); + } + break; + default: + foreach (Location invocationLocation in invocationLocations) { + context.ReportDiagnostic( + Diagnostic.Create( + descriptor: new( + id: "CSV0001", + title: "Unsupported property type", + messageFormat: "{0} is not supported by CsvSerializer.", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true + ), + location: invocationLocation, + messageArgs: underlyingTypeSymbol.Name + ) + ); + } + throw new DiagnosticReportedException(); + } + break; + default: + foreach (Location invocationLocation in invocationLocations) { + context.ReportDiagnostic( + Diagnostic.Create( + descriptor: new( + id: "CSV0001", + title: "Unsupported property type", + messageFormat: "{0} is not supported by CsvSerializer.", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true + ), + location: invocationLocation, + messageArgs: propertyTypeSymbol.Name + ) + ); + } + throw new DiagnosticReportedException(); + } + + // Deserializers + switch (propertyTypeSymbol) { + case { SpecialType: SpecialType.System_SByte }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && sbyte.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out sbyte v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (sbyte.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct sbyte format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && sbyte.TryParse(columns[{{col}}].Span[1..^1], out sbyte v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (sbyte.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct sbyte format."); + } + """); + break; + case { SpecialType: SpecialType.System_Byte }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && byte.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out byte v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (byte.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct byte format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && byte.TryParse(columns[{{col}}].Span[1..^1], out byte v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (byte.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct byte format."); + } + """); + break; + case { SpecialType: SpecialType.System_Int16 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && short.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out short v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (short.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct short format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && short.TryParse(columns[{{col}}].Span[1..^1], out short v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (short.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct short format."); + } + """); + break; + case { SpecialType: SpecialType.System_UInt16 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && ushort.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out ushort v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (ushort.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct ushort format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && ushort.TryParse(columns[{{col}}].Span[1..^1], out ushort v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (ushort.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct ushort format."); + } + """); + break; + case { SpecialType: SpecialType.System_Int32 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && int.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out int v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (int.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct int format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && int.TryParse(columns[{{col}}].Span[1..^1], out int v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (int.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct int format."); + } + """); + break; + case { SpecialType: SpecialType.System_UInt32 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && uint.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out uint v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (uint.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct uint format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && uint.TryParse(columns[{{col}}].Span[1..^1], out uint v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (uint.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct uint format."); + } + """); + break; + case { SpecialType: SpecialType.System_Int64 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && long.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out long v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (long.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct long format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && long.TryParse(columns[{{col}}].Span[1..^1], out long v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (long.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct long format."); + } + """); + break; + case { SpecialType: SpecialType.System_UInt64 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && ulong.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out ulong v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (ulong.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct ulong format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && ulong.TryParse(columns[{{col}}].Span[1..^1], out ulong v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (ulong.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct ulong format."); + } + """); + break; + case { SpecialType: SpecialType.System_Single }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && float.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Float, provider, out float v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (float.TryParse(columns[{{col}}].Span, NumberStyles.Float, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct float format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && float.TryParse(columns[{{col}}].Span[1..^1], out float v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (float.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct float format."); + } + """); + break; + case { SpecialType: SpecialType.System_Double }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && double.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Float, provider, out double v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (double.TryParse(columns[{{col}}].Span, NumberStyles.Float, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct double format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && double.TryParse(columns[{{col}}].Span[1..^1], out double v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (double.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct double format."); + } + """); + break; + case { SpecialType: SpecialType.System_Decimal }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && decimal.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Number, provider, out decimal v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (decimal.TryParse(columns[{{col}}].Span, NumberStyles.Number, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct decimal format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && decimal.TryParse(columns[{{col}}].Span[1..^1], out decimal v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (decimal.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct decimal format."); + } + """); + break; + case { SpecialType: SpecialType.System_Boolean }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && bool.TryParse(columns[{{col}}].Span[1..^1], out bool v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (bool.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct Boolean format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && bool.TryParse(columns[{{col}}].Span[1..^1], out bool v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (bool.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct Boolean format."); + } + """); + break; + case { SpecialType: SpecialType.System_String }: + deserializeWithProviderBuilder.AppendLine($$""" + string v{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (v{{propertySymbol.Name}}.StartsWith('"') + && v{{propertySymbol.Name}}.EndsWith('"')) { + v{{propertySymbol.Name}} = v{{propertySymbol.Name}}[1..^1]; + } + v{{propertySymbol.Name}} = v{{propertySymbol.Name}}.Replace("\"\"", "\"").TrimEnd('\r'); + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + string v{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (v{{propertySymbol.Name}}.StartsWith('"') + && v{{propertySymbol.Name}}.EndsWith('"')) { + v{{propertySymbol.Name}} = v{{propertySymbol.Name}}[1..^1]; + } + v{{propertySymbol.Name}} = v{{propertySymbol.Name}}.Replace("\"\"", "\"").TrimEnd('\r'); + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + """); + break; + case { TypeKind: TypeKind.Enum }: { + string fullEnumName = GetFullName(propertyTypeSymbol); + deserializeWithProviderBuilder.AppendLine($$""" + if (Enum.TryParse(columns[{{col}}].ToString(), out {{fullEnumName}} v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not a valid {{fullEnumName}} value."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (Enum.TryParse(columns[{{col}}].ToString(), out {{fullEnumName}} v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not a valid {{fullEnumName}} value."); + } + """); + break; + } + case { SpecialType: SpecialType.System_DateTime }: + if (dateFormat is not null) { + deserializeWithProviderBuilder.AppendLine($$""" + string s{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (s{{propertySymbol.Name}}.StartsWith('"') + && s{{propertySymbol.Name}}.EndsWith('"')) { + s{{propertySymbol.Name}} = s{{propertySymbol.Name}}[1..^1]; + } + if (DateTime.TryParseExact(s{{propertySymbol.Name}}, "{{dateFormat.Replace("\\", "\\\\")}}", provider, DateTimeStyles.AssumeLocal, out DateTime v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct DateTime format. Expected format was '{{dateFormat}}'."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + string s{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (s{{propertySymbol.Name}}.StartsWith('"') + && s{{propertySymbol.Name}}.EndsWith('"')) { + s{{propertySymbol.Name}} = s{{propertySymbol.Name}}[1..^1]; + } + if (DateTime.TryParseExact(s{{propertySymbol.Name}}, "{{dateFormat.Replace("\\", "\\\\")}}", null, DateTimeStyles.AssumeLocal, out DateTime v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct DateTime format. Expected format was '{{dateFormat}}'."); + } + """); + } else { + deserializeWithProviderBuilder.AppendLine($$""" + string s{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (s{{propertySymbol.Name}}.StartsWith('"') + && s{{propertySymbol.Name}}.EndsWith('"')) { + s{{propertySymbol.Name}} = s{{propertySymbol.Name}}[1..^1]; + } + if (DateTime.TryParse(s{{propertySymbol.Name}}, provider, DateTimeStyles.AssumeLocal, out DateTime v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct DateTime format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + string s{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (s{{propertySymbol.Name}}.StartsWith('"') + && s{{propertySymbol.Name}}.EndsWith('"')) { + s{{propertySymbol.Name}} = s{{propertySymbol.Name}}[1..^1]; + } + if (DateTime.TryParse(s{{propertySymbol.Name}}, null, DateTimeStyles.AssumeLocal, out DateTime v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct DateTime format."); + } + """); + } + break; + case { Name: "Uri", ContainingNamespace.Name: "System" }: + deserializeWithProviderBuilder.AppendLine($$""" + string v{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (v{{propertySymbol.Name}}.StartsWith('"') + && v{{propertySymbol.Name}}.EndsWith('"')) { + v{{propertySymbol.Name}} = v{{propertySymbol.Name}}[1..^1]; + } + v{{propertySymbol.Name}} = v{{propertySymbol.Name}}.Replace("\"\"", "\"").TrimEnd('\r'); + if (string.IsNullOrWhiteSpace(v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = null; + } else { + item.{{propertySymbol.Name}} = new Uri(v{{propertySymbol.Name}}); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + string v{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (v{{propertySymbol.Name}}.StartsWith('"') + && v{{propertySymbol.Name}}.EndsWith('"')) { + v{{propertySymbol.Name}} = v{{propertySymbol.Name}}[1..^1]; + } + v{{propertySymbol.Name}} = v{{propertySymbol.Name}}.Replace("\"\"", "\"").TrimEnd('\r'); + if (string.IsNullOrWhiteSpace(v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = null; + } else { + item.{{propertySymbol.Name}} = new Uri(v{{propertySymbol.Name}}); + } + """); + break; + case { IsValueType: true, NullableAnnotation: NullableAnnotation.Annotated }: + if (propertyTypeSymbol is not INamedTypeSymbol { TypeArguments: { Length: 1 } typeArguments }) { + break; + } + ITypeSymbol underlyingTypeSymbol = typeArguments[0]; + switch (underlyingTypeSymbol) { + case { SpecialType: SpecialType.System_SByte }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && sbyte.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out sbyte v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (sbyte.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct sbyte format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && sbyte.TryParse(columns[{{col}}].Span[1..^1], out sbyte v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (sbyte.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct sbyte format."); + } + """); + break; + case { SpecialType: SpecialType.System_Byte }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && byte.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out byte v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (byte.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct byte format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && byte.TryParse(columns[{{col}}].Span[1..^1], out byte v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (byte.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct byte format."); + } + """); + break; + case { SpecialType: SpecialType.System_Int16 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && short.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out short v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (short.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct short format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && short.TryParse(columns[{{col}}].Span[1..^1], out short v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (short.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct short format."); + } + """); + break; + case { SpecialType: SpecialType.System_UInt16 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && ushort.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out ushort v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (ushort.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct ushort format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && ushort.TryParse(columns[{{col}}].Span[1..^1], out ushort v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (ushort.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct ushort format."); + } + """); + break; + case { SpecialType: SpecialType.System_Int32 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && int.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out int v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (int.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct int format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && int.TryParse(columns[{{col}}].Span[1..^1], out int v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (int.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct int format."); + } + """); + break; + case { SpecialType: SpecialType.System_UInt32 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && uint.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out uint v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (uint.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct uint format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && uint.TryParse(columns[{{col}}].Span[1..^1], out uint v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (uint.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct uint format."); + } + """); + break; + case { SpecialType: SpecialType.System_Int64 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && long.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out long v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (long.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct long format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && long.TryParse(columns[{{col}}].Span[1..^1], out long v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (long.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct long format."); + } + """); + break; + case { SpecialType: SpecialType.System_UInt64 }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && ulong.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Integer, provider, out ulong v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (ulong.TryParse(columns[{{col}}].Span, NumberStyles.Integer, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct ulong format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && ulong.TryParse(columns[{{col}}].Span[1..^1], out ulong v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (ulong.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct ulong format."); + } + """); + break; + case { SpecialType: SpecialType.System_Single }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && float.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Float, provider, out float v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (float.TryParse(columns[{{col}}].Span, NumberStyles.Float, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct float format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && float.TryParse(columns[{{col}}].Span[1..^1], out float v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (float.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct float format."); + } + """); + break; + case { SpecialType: SpecialType.System_Double }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && double.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Float, provider, out double v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (double.TryParse(columns[{{col}}].Span, NumberStyles.Float, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct double format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && double.TryParse(columns[{{col}}].Span[1..^1], out double v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (double.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct double format."); + } + """); + break; + case { SpecialType: SpecialType.System_Decimal }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && decimal.TryParse(columns[{{col}}].Span[1..^1], NumberStyles.Number, provider, out decimal v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (decimal.TryParse(columns[{{col}}].Span, NumberStyles.Number, provider, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct decimal format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && decimal.TryParse(columns[{{col}}].Span[1..^1], out decimal v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (decimal.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct decimal format."); + } + """); + break; + case { SpecialType: SpecialType.System_Boolean }: + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && bool.TryParse(columns[{{col}}].Span[1..^1], out bool v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (bool.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct Boolean format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].Length >= 2 && columns[{{col}}].Span[0] == '"' && bool.TryParse(columns[{{col}}].Span[1..^1], out bool v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (bool.TryParse(columns[{{col}}].Span, out v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else if (columns[{{col}}].Length == 0) { + item.{{propertySymbol.Name}} = null; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct Boolean format."); + } + """); + break; + case { TypeKind: TypeKind.Enum }: { + string fullEnumName = GetFullName(underlyingTypeSymbol); + deserializeWithProviderBuilder.AppendLine($$""" + if (columns[{{col}}].ToString() is not { Length: > 0 } s{{propertySymbol.Name}}) { + item.{{propertySymbol.Name}} = null; + } else if (Enum.TryParse(s{{propertySymbol.Name}}, out {{fullEnumName}} v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", s{{propertySymbol.Name}}, "Input string was not a valid {{fullEnumName}} value."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + if (columns[{{col}}].ToString() is not { Length: > 0 } s{{propertySymbol.Name}}) { + item.{{propertySymbol.Name}} = null; + } else if (Enum.TryParse(s{{propertySymbol.Name}}, out {{fullEnumName}} v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", s{{propertySymbol.Name}}, "Input string was not a valid {{fullEnumName}} value."); + } + """); + break; + } + case { SpecialType: SpecialType.System_DateTime }: + if (dateFormat is not null) { + deserializeWithProviderBuilder.AppendLine($$""" + string s{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (s{{propertySymbol.Name}}.StartsWith('"') + && s{{propertySymbol.Name}}.EndsWith('"')) { + s{{propertySymbol.Name}} = s{{propertySymbol.Name}}[1..^1]; + } + if (s{{propertySymbol.Name}}.Length == 0) { + item.{{propertySymbol.Name}} = null; + } else if (DateTime.TryParseExact(s{{propertySymbol.Name}}, "{{dateFormat.Replace("\\", "\\\\")}}", provider, DateTimeStyles.AssumeLocal, out DateTime v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct DateTime format. Expected format was '{{dateFormat}}'."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + string s{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (s{{propertySymbol.Name}}.StartsWith('"') + && s{{propertySymbol.Name}}.EndsWith('"')) { + s{{propertySymbol.Name}} = s{{propertySymbol.Name}}[1..^1]; + } + if (s{{propertySymbol.Name}}.Length == 0) { + item.{{propertySymbol.Name}} = null; + } else if (DateTime.TryParseExact(s{{propertySymbol.Name}}, "{{dateFormat.Replace("\\", "\\\\")}}", null, DateTimeStyles.AssumeLocal, out DateTime v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct DateTime format. Expected format was '{{dateFormat}}'."); + } + """); + } else { + deserializeWithProviderBuilder.AppendLine($$""" + string s{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (s{{propertySymbol.Name}}.StartsWith('"') + && s{{propertySymbol.Name}}.EndsWith('"')) { + s{{propertySymbol.Name}} = s{{propertySymbol.Name}}[1..^1]; + } + if (s{{propertySymbol.Name}}.Length == 0) { + item.{{propertySymbol.Name}} = null; + } else if (DateTime.TryParse(s{{propertySymbol.Name}}, provider, DateTimeStyles.AssumeLocal, out DateTime v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct DateTime format."); + } + """); + deserializeWithoutProviderBuilder.AppendLine($$""" + string s{{propertySymbol.Name}} = columns[{{col}}].ToString().Trim(); + if (s{{propertySymbol.Name}}.StartsWith('"') + && s{{propertySymbol.Name}}.EndsWith('"')) { + s{{propertySymbol.Name}} = s{{propertySymbol.Name}}[1..^1]; + } + if (s{{propertySymbol.Name}}.Length == 0) { + item.{{propertySymbol.Name}} = null; + } else if (DateTime.TryParse(s{{propertySymbol.Name}}, null, DateTimeStyles.AssumeLocal, out DateTime v{{propertySymbol.Name}})) { + item.{{propertySymbol.Name}} = v{{propertySymbol.Name}}; + } else { + throw new CsvFormatException(typeof({{fullTypeName}}), "{{propertySymbol.Name}}", columns[{{col}}].ToString(), "Input string was not in correct DateTime format."); + } + """); + } + break; +#if DEBUG + default: + foreach (Location invocationLocation in invocationLocations) { + context.ReportDiagnostic( + Diagnostic.Create( + descriptor: new( + id: "CSV0001", + title: "Unsupported property type", + messageFormat: "{0} is not supported by CsvSerializer.", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true + ), + location: invocationLocation, + messageArgs: underlyingTypeSymbol.Name + ) + ); + } + throw new DiagnosticReportedException(); +#endif + } + break; +#if DEBUG + default: + foreach (Location invocationLocation in invocationLocations) { + context.ReportDiagnostic( + Diagnostic.Create( + descriptor: new( + id: "CSV0001", + title: "Unsupported property type", + messageFormat: "{0} is not supported by CsvSerializer.", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true + ), + location: invocationLocation, + messageArgs: propertyTypeSymbol.Name + ) + ); + } + throw new DiagnosticReportedException(); +#endif + } + col++; + } + + context.AddSource( + hintName: $"{serializerName}.g.cs", + sourceText: SourceText.From( + text: $$""" + #nullable enable + using System; + using System.Text; + + namespace Csv.Internal.NativeImpl { + internal sealed class {{serializerName}} : ISerializer { + public void SerializeHeader(char delimiter, StringBuilder stringBuilder) { + {{serializeHeaderBuilder.ToString().Trim()}} + stringBuilder.Append("\r\n"); + } + + public void SerializeItem(IFormatProvider? provider, char delimiter, StringBuilder stringBuilder, object item) { + {{fullTypeName}} i = ({{fullTypeName}})item; + if (provider is null) { + {{serializeItemWithoutProviderBuilder.ToString().Trim()}} + } else { + {{serializeItemWithProviderBuilder.ToString().Trim()}} + } + stringBuilder.Append("\r\n"); + } + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + + context.AddSource( + hintName: $"{deserializerName}.g.cs", + sourceText: SourceText.From( + text: $$""" + #nullable enable + using System; + using System.Collections.Generic; + using System.Globalization; + + namespace Csv.Internal.NativeImpl { + internal sealed class {{deserializerName}} : IDeserializer { + public List Deserialize(IFormatProvider? provider, char delimiter, bool skipHeader, ReadOnlyMemory csv) { + List items = new(); + bool firstRow = true; + while (csv.Length > 0) { + List> columns = StringSplitter.ReadNextLine(ref csv, delimiter); + if (firstRow) { + firstRow = false; + if (skipHeader) { + continue; + } + } + if (columns.Count != {{propertySymbols.Count}}) { + int endOfLine = csv.Span.IndexOf('\n'); + string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString(); + throw new CsvFormatException(typeof({{fullTypeName}}), line, "Row must consists of {{propertySymbols.Count}} columns."); + } + {{fullTypeName}} item = Activator.CreateInstance<{{fullTypeName}}>(); + if (provider is null) { + {{deserializeWithoutProviderBuilder.ToString().Trim()}} + } else { + {{deserializeWithProviderBuilder.ToString().Trim()}} + } + items.Add(item); + } + return items; + } + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + + return ( + FullTypeName: fullTypeName, + SerializerName: serializerName, + DeserializerName: deserializerName + ); + } + + private static string GetFullName(ITypeSymbol typeSymbol) { + INamespaceSymbol? @namespace; + string fullName; + if (typeSymbol.ContainingType is { } containingType) { + fullName = $"{containingType.Name}.{typeSymbol.Name}"; + @namespace = containingType.ContainingNamespace; + } else { + fullName = typeSymbol.Name; + @namespace = typeSymbol.ContainingNamespace; + } + + while (@namespace is { Name.Length: > 0 }) { + fullName = $"{@namespace.Name}.{fullName}"; + @namespace = @namespace.ContainingNamespace; + } + + return fullName; + } + } +} diff --git a/CsvSerializer/Internals/SerializeOrDeserializeSyntaxReceiver.cs b/CsvSerializer/Internals/SerializeOrDeserializeSyntaxReceiver.cs new file mode 100644 index 0000000..38053ff --- /dev/null +++ b/CsvSerializer/Internals/SerializeOrDeserializeSyntaxReceiver.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Csv.Internals { + internal sealed class SerializeOrDeserializeSyntaxReceiver : ISyntaxContextReceiver { + public readonly Dictionary> InvocationLocationsByTypeSymbol = new(SymbolEqualityComparer.Default); + + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { + // CsvSerializer.Serialize() or + // Csv.CsvSerializer.Serialize() + if (context.Node is InvocationExpressionSyntax { + Expression: MemberAccessExpressionSyntax { + Expression: IdentifierNameSyntax { + Identifier.Text: "CsvSerializer" + } or MemberAccessExpressionSyntax { + Expression: IdentifierNameSyntax { + Identifier.Text: "Csv" + }, + Name.Identifier.Text: "CsvSerializer" + }, + Name.Identifier.Text: "Serialize" + }, + ArgumentList.Arguments.Count: > 0 + } serializeInvocation) { + // Resolve into symbol + IMethodSymbol? methodSymbol = context.SemanticModel.GetSymbolInfo(serializeInvocation).Symbol as IMethodSymbol; + + // Make sure it's Csv.CsvSerializer.Serialize() + if (methodSymbol is not { + ContainingType: { + Name: "CsvSerializer", + ContainingNamespace.Name: "Csv" + }, + Name: "Serialize" + }) { + return; + } + + // Make sure type argument is resolved and the type is explicitly declared + if (methodSymbol.TypeArguments is not { + Length: 1 + } typeArguments + || typeArguments[0] is not { + Name.Length: > 0, + DeclaringSyntaxReferences.Length: > 0 + }) { + return; + } + + RegisterInvocation(typeArguments[0], serializeInvocation.GetLocation()); + } + + // CsvSerializer.Deserialize() or + // Csv.CsvSerializer.Deserialize() + if (context.Node is InvocationExpressionSyntax { + Expression: MemberAccessExpressionSyntax { + Expression: IdentifierNameSyntax { + Identifier.Text: "CsvSerializer" + } or MemberAccessExpressionSyntax { + Expression: IdentifierNameSyntax { + Identifier.Text: "Csv" + }, + Name.Identifier.Text: "CsvSerializer" + }, + Name.Identifier.Text: "Deserialize" + }, + ArgumentList.Arguments.Count: > 0 + } deserializeInvocation) { + // Resolve into symbol + IMethodSymbol? methodSymbol = context.SemanticModel.GetSymbolInfo(deserializeInvocation).Symbol as IMethodSymbol; + + // Make sure it's Csv.CsvSerializer.Deserialize() + if (methodSymbol is not { + ContainingType: { + Name: "CsvSerializer", + ContainingNamespace.Name: "Csv" + }, + Name: "Deserialize" + }) { + return; + } + + // Make sure type argument is resolved and the type is explicitly declared + if (methodSymbol.TypeArguments is not { + Length: 1 + } typeArguments + || typeArguments[0] is not { + Name.Length: > 0, + DeclaringSyntaxReferences.Length: > 0 + }) { + return; + } + + RegisterInvocation(typeArguments[0], deserializeInvocation.GetLocation()); + } + } + + private void RegisterInvocation(ITypeSymbol typeSymbol, Location invocationLocation) { + if (!InvocationLocationsByTypeSymbol.TryGetValue(typeSymbol, out List? invocationLocations)) { + invocationLocations = new List(); + InvocationLocationsByTypeSymbol.Add(typeSymbol, invocationLocations); + } + invocationLocations.Add(invocationLocation); + } + } +} diff --git a/CsvSerializer/Internals/StaticSourceFilesGenerator.cs b/CsvSerializer/Internals/StaticSourceFilesGenerator.cs new file mode 100644 index 0000000..a3cc680 --- /dev/null +++ b/CsvSerializer/Internals/StaticSourceFilesGenerator.cs @@ -0,0 +1,985 @@ +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace Csv.Internals { + internal static class StaticSourceFilesGenerator { + public static void AddCsvSerializer(this GeneratorPostInitializationContext context) { + context.AddSource( + hintName: "CsvSerializer.g.cs", + sourceText: SourceText.From( + text: """ + #nullable enable + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text; + + namespace Csv { + public static class CsvSerializer { + public static string Serialize( + IEnumerable items, + bool withHeaders = false, + char delimiter = ',', + IFormatProvider? provider = null + ) where T : notnull { + ISerializer serializer = SerializerFactory.GetOrCreateSerializer(); + StringBuilder stringBuilder = new(); + if (withHeaders) { + serializer.SerializeHeader(delimiter, stringBuilder); + } + foreach (T item in items) { + serializer.SerializeItem(provider, delimiter, stringBuilder, item); + } + return stringBuilder.ToString().TrimEnd(); + } + + public static T[] Deserialize(string csv, bool hasHeaders = false, char delimiter = ',', IFormatProvider? provider = null) where T : notnull { + IDeserializer deserializer = SerializerFactory.GetOrCreateDeserializer(); + List items = deserializer.Deserialize(provider, delimiter, hasHeaders, csv.AsMemory()); + return items.Cast().ToArray(); + } + } + } + + """, + encoding: Encoding.UTF8 + )); + } + + public static void AddAttributes(this GeneratorPostInitializationContext context) { + context.AddSource( + hintName: "Attributes.g.cs", + sourceText: SourceText.From( + text: """ + #nullable enable + using System; + + namespace Csv { + [AttributeUsage(AttributeTargets.Property)] + public sealed class CsvColumnAttribute : Attribute { + public string Name { get; } + public string? DateFormat { get; set; } + + public CsvColumnAttribute(string name) { + Name = name; + } + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + } + + public static void AddExceptions(this GeneratorPostInitializationContext context) { + context.AddSource( + hintName: "Exceptions.g.cs", + sourceText: SourceText.From( + text: """ + #nullable enable + using System; + + namespace Csv { + public abstract class CsvException : Exception { + public CsvException() { + } + + public CsvException(string? message) : base(message) { + } + + public CsvException(string? message, Exception? innerException) : base(message, innerException) { + } + } + + public sealed class CsvTypeException : CsvException { + public Type Type { get; } + public string? PropertyName { get; } + + public CsvTypeException(Type type) : base($"{type.Name} cannot be used in serialization or deserialization.") { + Type = type; + } + + public CsvTypeException(Type type, string? message) : base($"{type.Name} cannot be used in serialization or deserialization. {message}") { + Type = type; + } + + public CsvTypeException(Type type, string propertyName, string? message) : base($"{type.Name}.{propertyName} cannot be used in serialization or deserialization. {message}") { + Type = type; + PropertyName = propertyName; + } + + public CsvTypeException(Type type, string propertyName, string? message, Exception? innerException) : base($"{type.Name}.{propertyName} cannot be used in serialization or deserialization. {message}", innerException) { + Type = type; + PropertyName = propertyName; + } + } + + public sealed class CsvPropertyTypeException : CsvException { + public Type PropertyType { get; } + + public CsvPropertyTypeException(Type propertyType) : base($"Property of type {propertyType.Name} cannot be used in serialization or deserialization.") { + PropertyType = propertyType; + } + + public CsvPropertyTypeException(Type propertyType, string? message) : base($"Property of type {propertyType.Name} cannot be used in serialization or deserialization. {message}") { + PropertyType = propertyType; + } + + public CsvPropertyTypeException(Type propertyType, string? message, Exception? innerException) : base($"Property of type {propertyType.Name} cannot be used in serialization or deserialization. {message}", innerException) { + PropertyType = propertyType; + } + } + + public sealed class CsvFormatException : CsvException { + public Type? Type { get; } + public string? PropertyName { get; } + public string? Value { get; } + + public CsvFormatException(string value, string? message) : base($"Cannot deserialize '{value}'. {message}") { + Value = value; + } + + public CsvFormatException(Type type, string value, string? message) : base($"Cannot deserialize '{value}' into {type.Name}. {message}") { + Type = type; + Value = value; + } + + public CsvFormatException(Type type, string propertyName, string value, string? message) : base($"Cannot deserialize '{value}' into {type.Name}.{propertyName}. {message}") { + Type = type; + PropertyName = propertyName; + Value = value; + } + + public CsvFormatException(Type type, string propertyName, string value, string? message, Exception? innerException) : base($"Cannot deserialize '{value}' into {type.Name}.{propertyName}. {message}", innerException) { + Type = type; + PropertyName = propertyName; + Value = value; + } + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + } + + public static void AddISerializer(this GeneratorPostInitializationContext context) { + context.AddSource( + hintName: "ISerializer.g.cs", + sourceText: SourceText.From( + text: """ + #nullable enable + using System; + using System.Text; + + namespace Csv { + internal interface ISerializer { + void SerializeHeader(char delimiter, StringBuilder stringBuilder); + void SerializeItem(IFormatProvider? provider, char delimiter, StringBuilder stringBuilder, object item); + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + } + + public static void AddIDeserializer(this GeneratorPostInitializationContext context) { + context.AddSource( + hintName: "IDeserializer.g.cs", + sourceText: SourceText.From( + text: """ + #nullable enable + using System; + using System.Collections.Generic; + + namespace Csv { + internal interface IDeserializer { + List Deserialize(IFormatProvider? provider, char delimiter, bool skipHeader, ReadOnlyMemory csv); + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + } + + public static void AddIConverter(this GeneratorPostInitializationContext context) { + context.AddSource( + hintName: "IConverter.g.cs", + sourceText: SourceText.From( + text: """ + #nullable enable + using System; + using System.Text; + + namespace Csv.Internal { + internal interface IConverter { + void AppendToStringBuilder(StringBuilder stringBuilder, IFormatProvider provider, T value, CsvColumnAttribute? attribute, char delimiter); + T Deserialize(ReadOnlyMemory text, IFormatProvider provider, CsvColumnAttribute? attribute); + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + } + + public static void AddStringSplitter(this GeneratorPostInitializationContext context) { + context.AddSource( + hintName: "StringSplitter.g.cs", + sourceText: SourceText.From( + text: """ + #nullable enable + using System; + using System.Collections.Generic; + + namespace Csv.Internal { + internal static class StringSplitter { + private enum ParserState { + InStartingWhiteSpace, + InUnquotedValue, + InQuotedValue, + InEscapeSequence, + InTrailingWhiteSpace + } + + public static List> ReadNextLine(ref ReadOnlyMemory csv, char separator = ',') { + ReadOnlySpan span = csv.Span; + List> columns = new(); + int startOfLiteral = 0; + int endOfLiteral = 0; + ParserState state = ParserState.InStartingWhiteSpace; + for (int i = 0, length = csv.Length; i <= length; i++) { + if (i == length) { + switch (state) { + case ParserState.InStartingWhiteSpace: + case ParserState.InUnquotedValue: + case ParserState.InEscapeSequence: + columns.Add(csv[startOfLiteral..i]); + csv = csv.Slice(csv.Length - 1, 0); + return columns; + case ParserState.InQuotedValue: + throw new CsvFormatException(csv.ToString(), "End of file in quoted literal."); + case ParserState.InTrailingWhiteSpace: + columns.Add(csv.Slice(startOfLiteral, endOfLiteral - startOfLiteral + 1)); + csv = csv.Slice(csv.Length - 1, 0); + return columns; + } + } else { + switch (span[i]) { + case '"': + switch (state) { + case ParserState.InStartingWhiteSpace: + startOfLiteral = i; + state = ParserState.InQuotedValue; + break; + case ParserState.InUnquotedValue: + int endOfLine = span.IndexOf('\n'); + string line = endOfLine == -1 ? csv.ToString() : csv[..endOfLine].ToString(); + throw new CsvFormatException(line, $"Invalid character at position {i}: \""); + case ParserState.InQuotedValue: + state = ParserState.InEscapeSequence; + break; + case ParserState.InEscapeSequence: + state = ParserState.InQuotedValue; + break; + case ParserState.InTrailingWhiteSpace: + endOfLine = span.IndexOf('\n'); + line = endOfLine == -1 ? csv.ToString() : csv[..endOfLine].ToString(); + throw new CsvFormatException(line, $"Invalid character at position {i}: \""); + } + break; + case char c when c == separator: + switch (state) { + case ParserState.InStartingWhiteSpace: + case ParserState.InUnquotedValue: + case ParserState.InEscapeSequence: + columns.Add(csv[startOfLiteral..i]); + startOfLiteral = i + 1; + state = ParserState.InStartingWhiteSpace; + break; + case ParserState.InTrailingWhiteSpace: + columns.Add(csv.Slice(startOfLiteral, endOfLiteral - startOfLiteral + 1)); + startOfLiteral = i + 1; + state = ParserState.InStartingWhiteSpace; + break; + } + break; + case '\n': + switch (state) { + case ParserState.InStartingWhiteSpace: + case ParserState.InUnquotedValue: + case ParserState.InEscapeSequence: + columns.Add(csv[startOfLiteral..i]); + csv = csv[(i + 1)..]; + return columns; + case ParserState.InTrailingWhiteSpace: + columns.Add(csv.Slice(startOfLiteral, endOfLiteral - startOfLiteral + 1)); + csv = csv[(i + 1)..]; + return columns; + } + break; + case char c: + switch (state) { + case ParserState.InStartingWhiteSpace: + state = ParserState.InUnquotedValue; + break; + case ParserState.InEscapeSequence: + endOfLiteral = i - 1; + state = ParserState.InTrailingWhiteSpace; + break; + case ParserState.InTrailingWhiteSpace: + if (!char.IsWhiteSpace(c)) { + int endOfLine = span.IndexOf('\n'); + string line = endOfLine == -1 ? csv.ToString() : csv[..endOfLine].ToString(); + throw new CsvFormatException(line, $"Invalid character at position {i}: {c}"); + } + break; + } + break; + } + } + } + throw new InvalidOperationException("Parser internal error."); + } + + public static List OldSplitLine(string line, char separator = ',') { + List columns = new(); + int i = 0; + int startOfLiteral; + int endOfLiteral; + + EXPECTING_TOKEN: + if (i == line.Length) { + columns.Add(string.Empty); + return columns; + } + switch (line[i]) { + case ' ': + i++; + goto EXPECTING_TOKEN; + case '"': + goto IN_STRING_LITERAL; + case char c: + if (c == separator) { + columns.Add(string.Empty); + i++; + goto EXPECTING_TOKEN; + } else if (char.IsLetterOrDigit(c) || c == '.' || c == '-' || c == '+') { + goto IN_VALUE_LITERAL; + } else { + throw new CsvFormatException(line, $"Invalid character at position {i}: {c}"); + } + } + + IN_STRING_LITERAL: + { + startOfLiteral = i++; + bool maybeInEscapeCharacter = false; + IN_STRING_LITERAL_LOOP: + if (i == line.Length) { + if (maybeInEscapeCharacter) { + columns.Add(line[startOfLiteral..i]); + return columns; + } else { + throw new CsvFormatException(line, "Newline in string literal."); + } + } + if (maybeInEscapeCharacter) { + maybeInEscapeCharacter = false; + if (line[i] != '"') { + endOfLiteral = i; + goto HAS_LITERAL_LOOP; + } + } else if (line[i] == '"') { + maybeInEscapeCharacter = true; + } + i++; + goto IN_STRING_LITERAL_LOOP; + } + + IN_VALUE_LITERAL: + { + startOfLiteral = i++; + IN_VALUE_LITERAL_LOOP: + if (i == line.Length) { + columns.Add(string.Empty); + return columns; + } + switch (line[i]) { + case ' ': + endOfLiteral = i++; + goto HAS_LITERAL_LOOP; + case char c: + if (c == separator) { + columns.Add(line[startOfLiteral..i]); + i++; + goto EXPECTING_TOKEN; + } else if (char.IsLetterOrDigit(c) || c == '.' || c == '-' || c == '+') { + i++; + goto IN_VALUE_LITERAL_LOOP; + } else { + throw new CsvFormatException(line, $"Invalid character at position {i}: {c}"); + } + } + } + + HAS_LITERAL_LOOP: + if (i == line.Length) { + columns.Add(line[startOfLiteral..endOfLiteral]); + return columns; + } + switch (line[i]) { + case ' ': + i++; + goto HAS_LITERAL_LOOP; + case char c: + if (c == separator) { + columns.Add(line[startOfLiteral..endOfLiteral]); + i++; + goto EXPECTING_TOKEN; + } else { + throw new CsvFormatException(line, $"Invalid character at position {i}: {c}"); + } + } + } + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + } + + public static void AddNaiveSerializer(this GeneratorPostInitializationContext context) { + context.AddSource( + hintName: "NaiveSerializer.g.cs", + sourceText: SourceText.From( + text: """ + #nullable enable + using System; + using System.Reflection; + using System.Text; + + namespace Csv.Internal.NaiveImpl { + internal sealed class NaiveSerializer : ISerializer where T : notnull { + private enum SerializeAs { + Number, + String, + DateTime, + Uri, + Enum + } + + private readonly PropertyInfo[] _properties; + private readonly CsvColumnAttribute?[] _columnAttributes; + private readonly SerializeAs[] _serializeAs; + + public NaiveSerializer() { + _properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); + _columnAttributes = new CsvColumnAttribute?[_properties.Length]; + _serializeAs = new SerializeAs[_properties.Length]; + for (int i = 0; i < _properties.Length; i++) { + _columnAttributes[i] = _properties[i].GetCustomAttribute(); + switch (_properties[i].PropertyType) { + case Type tSByte when tSByte == typeof(sbyte): + case Type tNullableSByte when Nullable.GetUnderlyingType(tNullableSByte) == typeof(sbyte): + case Type tByte when tByte == typeof(byte): + case Type tNullableByte when Nullable.GetUnderlyingType(tNullableByte) == typeof(byte): + case Type tInt16 when tInt16 == typeof(short): + case Type tNullableInt16 when Nullable.GetUnderlyingType(tNullableInt16) == typeof(short): + case Type tUInt16 when tUInt16 == typeof(ushort): + case Type tNullableUInt16 when Nullable.GetUnderlyingType(tNullableUInt16) == typeof(ushort): + case Type tInt32 when tInt32 == typeof(int): + case Type tNullableInt32 when Nullable.GetUnderlyingType(tNullableInt32) == typeof(int): + case Type tUint32 when tUint32 == typeof(uint): + case Type tNullableUint32 when Nullable.GetUnderlyingType(tNullableUint32) == typeof(uint): + case Type tInt64 when tInt64 == typeof(long): + case Type tNullableInt64 when Nullable.GetUnderlyingType(tNullableInt64) == typeof(long): + case Type tUInt64 when tUInt64 == typeof(ulong): + case Type tNullableUInt64 when Nullable.GetUnderlyingType(tNullableUInt64) == typeof(ulong): + case Type tSingle when tSingle == typeof(float): + case Type tNullableSingle when Nullable.GetUnderlyingType(tNullableSingle) == typeof(float): + case Type tDouble when tDouble == typeof(double): + case Type tNullableDouble when Nullable.GetUnderlyingType(tNullableDouble) == typeof(double): + case Type tDecimal when tDecimal == typeof(decimal): + case Type tNullableDecimal when Nullable.GetUnderlyingType(tNullableDecimal) == typeof(decimal): + case Type tBoolean when tBoolean == typeof(bool): + case Type tNullableBoolean when Nullable.GetUnderlyingType(tNullableBoolean) == typeof(bool): + _serializeAs[i] = SerializeAs.Number; + break; + case Type tString when tString == typeof(string): + _serializeAs[i] = SerializeAs.String; + break; + case Type tDateTime when tDateTime == typeof(DateTime): + case Type tNullableDateTime when Nullable.GetUnderlyingType(tNullableDateTime) == typeof(DateTime): + _serializeAs[i] = SerializeAs.DateTime; + break; + case Type tUri when tUri == typeof(Uri): + _serializeAs[i] = SerializeAs.Uri; + break; + case Type tEnum when tEnum.IsEnum: + _serializeAs[i] = SerializeAs.Enum; + break; + case Type tNullableEnum when Nullable.GetUnderlyingType(tNullableEnum)?.IsEnum == true: + _serializeAs[i] = SerializeAs.Enum; + break; + default: + throw new CsvTypeException(_properties[i].PropertyType); + } + } + } + + public void SerializeHeader(char delimiter, StringBuilder stringBuilder) { + bool firstProperty = true; + for (int i = 0; i < _properties.Length; i++) { + if (!firstProperty) { + stringBuilder.Append(delimiter); + } + stringBuilder.Append('"'); + stringBuilder.Append((_columnAttributes[i]?.Name ?? _properties[i].Name).Replace("\"", "\"\"")); + stringBuilder.Append('"'); + firstProperty = false; + } + stringBuilder.Append("\r\n"); + } + + public void SerializeItem(IFormatProvider? provider, char delimiter, StringBuilder stringBuilder, object item) { + bool firstProperty = true; + for (int i = 0; i < _properties.Length; i++) { + if (!firstProperty) { + stringBuilder.Append(delimiter); + } + switch (_serializeAs[i]) { + case SerializeAs.Number: + string? str = Convert.ToString(_properties[i].GetValue(item), provider); + if (str is string && str.Contains(delimiter)) { + stringBuilder.AppendFormat(provider, "\"{0}\"", str); + } else { + stringBuilder.AppendFormat(provider, "{0}", str); + } + break; + case SerializeAs.String: + if (((string?)_properties[i].GetValue(item))?.Replace("\"", "\"\"") is string stringValue) { + stringBuilder.Append('"'); + stringBuilder.Append(stringValue); + stringBuilder.Append('"'); + } + break; + case SerializeAs.DateTime: + if (((DateTime?)_properties[i].GetValue(item)) is DateTime dateTimeValue) { + stringBuilder.Append('"'); + if (_columnAttributes[i]?.DateFormat is string dateFormat) { + stringBuilder.Append(dateTimeValue.ToString(dateFormat, provider)); + } else { + stringBuilder.Append(dateTimeValue.ToString(provider)); + } + stringBuilder.Append('"'); + } + break; + case SerializeAs.Uri: + if (((Uri?)_properties[i].GetValue(item)) is Uri uri && uri.ToString().Replace("\"", "\"\"") is string uriString) { + stringBuilder.Append('"'); + stringBuilder.Append(uriString); + stringBuilder.Append('"'); + } + break; + case SerializeAs.Enum: + stringBuilder.AppendFormat(provider, "{0}", _properties[i].GetValue(item)); + break; + default: + throw new NotImplementedException(); + } + firstProperty = false; + } + stringBuilder.Append("\r\n"); + } + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + } + + public static void AddNaiveDeserializer(this GeneratorPostInitializationContext context) { + context.AddSource( + hintName: "NaiveDeserializer.g.cs", + sourceText: SourceText.From( + text: """ + #nullable enable + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Reflection; + + namespace Csv.Internal.NaiveImpl { + internal sealed class NaiveDeserializer : IDeserializer where T : notnull { + private enum DeserializeAs { + Boolean, + SByte, + Byte, + Int16, + UInt16, + Int32, + UInt32, + Int64, + UInt64, + Single, + Double, + Decimal, + String, + DateTime, + Uri, + Enum + } + + private readonly PropertyInfo[] _properties; + private readonly CsvColumnAttribute?[] _columnAttributes; + private readonly DeserializeAs[] _deserializeAs; + private readonly bool[] _isNullable; + + public NaiveDeserializer() { + _properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); + _columnAttributes = new CsvColumnAttribute?[_properties.Length]; + _deserializeAs = new DeserializeAs[_properties.Length]; + _isNullable = new bool[_properties.Length]; + for (int i = 0; i < _properties.Length; i++) { + _columnAttributes[i] = _properties[i].GetCustomAttribute(); + switch (_properties[i].PropertyType) { + case Type tSByte when tSByte == typeof(sbyte): + _deserializeAs[i] = DeserializeAs.SByte; + _isNullable[i] = false; + break; + case Type tNullableSByte when Nullable.GetUnderlyingType(tNullableSByte) == typeof(sbyte): + _deserializeAs[i] = DeserializeAs.SByte; + _isNullable[i] = true; + break; + case Type tByte when tByte == typeof(byte): + _deserializeAs[i] = DeserializeAs.Byte; + _isNullable[i] = false; + break; + case Type tNullableByte when Nullable.GetUnderlyingType(tNullableByte) == typeof(byte): + _deserializeAs[i] = DeserializeAs.Byte; + _isNullable[i] = true; + break; + case Type tInt16 when tInt16 == typeof(short): + _deserializeAs[i] = DeserializeAs.Int16; + _isNullable[i] = false; + break; + case Type tNullableInt16 when Nullable.GetUnderlyingType(tNullableInt16) == typeof(short): + _deserializeAs[i] = DeserializeAs.Int16; + _isNullable[i] = true; + break; + case Type tUInt16 when tUInt16 == typeof(ushort): + _deserializeAs[i] = DeserializeAs.UInt16; + _isNullable[i] = false; + break; + case Type tNullableUInt16 when Nullable.GetUnderlyingType(tNullableUInt16) == typeof(ushort): + _deserializeAs[i] = DeserializeAs.UInt16; + _isNullable[i] = true; + break; + case Type tInt32 when tInt32 == typeof(int): + _deserializeAs[i] = DeserializeAs.Int32; + _isNullable[i] = false; + break; + case Type tNullableInt32 when Nullable.GetUnderlyingType(tNullableInt32) == typeof(int): + _deserializeAs[i] = DeserializeAs.Int32; + _isNullable[i] = true; + break; + case Type tUint32 when tUint32 == typeof(uint): + _deserializeAs[i] = DeserializeAs.UInt32; + _isNullable[i] = false; + break; + case Type tNullableUint32 when Nullable.GetUnderlyingType(tNullableUint32) == typeof(uint): + _deserializeAs[i] = DeserializeAs.UInt32; + _isNullable[i] = true; + break; + case Type tInt64 when tInt64 == typeof(long): + _deserializeAs[i] = DeserializeAs.Int64; + _isNullable[i] = false; + break; + case Type tNullableInt64 when Nullable.GetUnderlyingType(tNullableInt64) == typeof(long): + _deserializeAs[i] = DeserializeAs.Int64; + _isNullable[i] = true; + break; + case Type tUInt64 when tUInt64 == typeof(ulong): + _deserializeAs[i] = DeserializeAs.UInt64; + _isNullable[i] = false; + break; + case Type tNullableUInt64 when Nullable.GetUnderlyingType(tNullableUInt64) == typeof(ulong): + _deserializeAs[i] = DeserializeAs.UInt64; + _isNullable[i] = true; + break; + case Type tSingle when tSingle == typeof(float): + _deserializeAs[i] = DeserializeAs.Single; + _isNullable[i] = false; + break; + case Type tNullableSingle when Nullable.GetUnderlyingType(tNullableSingle) == typeof(float): + _deserializeAs[i] = DeserializeAs.Single; + _isNullable[i] = true; + break; + case Type tDouble when tDouble == typeof(double): + _deserializeAs[i] = DeserializeAs.Double; + _isNullable[i] = false; + break; + case Type tNullableDouble when Nullable.GetUnderlyingType(tNullableDouble) == typeof(double): + _deserializeAs[i] = DeserializeAs.Double; + _isNullable[i] = true; + break; + case Type tDecimal when tDecimal == typeof(decimal): + _deserializeAs[i] = DeserializeAs.Decimal; + _isNullable[i] = false; + break; + case Type tNullableDecimal when Nullable.GetUnderlyingType(tNullableDecimal) == typeof(decimal): + _deserializeAs[i] = DeserializeAs.Decimal; + _isNullable[i] = true; + break; + case Type tBoolean when tBoolean == typeof(bool): + _deserializeAs[i] = DeserializeAs.Boolean; + _isNullable[i] = false; + break; + case Type tNullableBoolean when Nullable.GetUnderlyingType(tNullableBoolean) == typeof(bool): + _deserializeAs[i] = DeserializeAs.Boolean; + _isNullable[i] = true; + break; + case Type tString when tString == typeof(string): + _deserializeAs[i] = DeserializeAs.String; + _isNullable[i] = true; + break; + case Type tDateTime when tDateTime == typeof(DateTime): + _deserializeAs[i] = DeserializeAs.DateTime; + _isNullable[i] = false; + break; + case Type tNullableDateTime when Nullable.GetUnderlyingType(tNullableDateTime) == typeof(DateTime): + _deserializeAs[i] = DeserializeAs.DateTime; + _isNullable[i] = true; + break; + case Type tUri when tUri == typeof(Uri): + _deserializeAs[i] = DeserializeAs.Uri; + _isNullable[i] = true; + break; + case Type tEnum when tEnum.IsEnum: + _deserializeAs[i] = DeserializeAs.Enum; + _isNullable[i] = false; + break; + case Type tNullableEnum when Nullable.GetUnderlyingType(tNullableEnum)?.IsEnum == true: + _deserializeAs[i] = DeserializeAs.Enum; + _isNullable[i] = true; + break; + default: + throw new CsvTypeException(_properties[i].PropertyType); + } + } + } + + public List Deserialize(IFormatProvider? provider, char delimiter, bool skipHeader, ReadOnlyMemory csv) { + bool firstRow = true; + List items = new(); + while (csv.Length > 0) { + List> columns = StringSplitter.ReadNextLine(ref csv, delimiter); + if (firstRow && skipHeader) { + firstRow = false; + continue; + } + if (_properties.Length != columns.Count) { + int endOfLine = csv.Span.IndexOf('\n'); + string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString(); + throw new CsvFormatException(typeof(T), line, $"Row must consists of {_properties.Length} columns."); + } + T item = Activator.CreateInstance(); + for (int i = 0; i < _properties.Length; i++) { + switch (_deserializeAs[i]) { + case DeserializeAs.SByte: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && sbyte.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out sbyte vSByte)) { + _properties[i].SetValue(item, vSByte); + } else if (sbyte.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vSByte)) { + _properties[i].SetValue(item, vSByte); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct sbyte format."); + } + break; + case DeserializeAs.Byte: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && byte.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out byte vByte)) { + _properties[i].SetValue(item, vByte); + } else if (byte.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vByte)) { + _properties[i].SetValue(item, vByte); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct byte format."); + } + break; + case DeserializeAs.Int16: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && short.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out short vInt16)) { + _properties[i].SetValue(item, vInt16); + } else if (short.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vInt16)) { + _properties[i].SetValue(item, vInt16); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Int16 format."); + } + break; + case DeserializeAs.UInt16: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && ushort.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out ushort vUInt16)) { + _properties[i].SetValue(item, vUInt16); + } else if (ushort.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vUInt16)) { + _properties[i].SetValue(item, vUInt16); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct UInt16 format."); + } + break; + case DeserializeAs.Int32: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && int.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out int vInt32)) { + _properties[i].SetValue(item, vInt32); + } else if (int.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vInt32)) { + _properties[i].SetValue(item, vInt32); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Int32 format."); + } + break; + case DeserializeAs.UInt32: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && uint.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out uint vUInt32)) { + _properties[i].SetValue(item, vUInt32); + } else if (uint.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vUInt32)) { + _properties[i].SetValue(item, vUInt32); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct UInt32 format."); + } + break; + case DeserializeAs.Int64: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && long.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out long vInt64)) { + _properties[i].SetValue(item, vInt64); + } else if (long.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vInt64)) { + _properties[i].SetValue(item, vInt64); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Int64 format."); + } + break; + case DeserializeAs.UInt64: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && ulong.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out ulong vUInt64)) { + _properties[i].SetValue(item, vUInt64); + } else if (ulong.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vUInt64)) { + _properties[i].SetValue(item, vUInt64); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct UInt64 format."); + } + break; + case DeserializeAs.Single: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && float.TryParse(columns[i].Span[1..^1], NumberStyles.Float, provider, out float vSingle)) { + _properties[i].SetValue(item, vSingle); + } else if (float.TryParse(columns[i].Span, NumberStyles.Float, provider, out vSingle)) { + _properties[i].SetValue(item, vSingle); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct floating point format."); + } + break; + case DeserializeAs.Double: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && double.TryParse(columns[i].Span[1..^1], NumberStyles.Float, provider, out double vDouble)) { + _properties[i].SetValue(item, vDouble); + } else if (double.TryParse(columns[i].Span, NumberStyles.Float, provider, out vDouble)) { + _properties[i].SetValue(item, vDouble); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct floating point format."); + } + break; + case DeserializeAs.Decimal: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && decimal.TryParse(columns[i].Span[1..^1], NumberStyles.Number, provider, out decimal vDecimal)) { + _properties[i].SetValue(item, vDecimal); + } else if (decimal.TryParse(columns[i].Span, NumberStyles.Number, provider, out vDecimal)) { + _properties[i].SetValue(item, vDecimal); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct decimal format."); + } + break; + case DeserializeAs.Boolean: + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && bool.TryParse(columns[i].Span[1..^1], out bool vBoolean)) { + _properties[i].SetValue(item, vBoolean); + } else if (bool.TryParse(columns[i].Span, out vBoolean)) { + _properties[i].SetValue(item, vBoolean); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Boolean format."); + } + break; + case DeserializeAs.String: + string s = columns[i].ToString().Trim(); + if (s.StartsWith('"') + && s.EndsWith('"')) { + s = s[1..^1]; + } + s = s.Replace("\"\"", "\"").TrimEnd('\r'); + _properties[i].SetValue(item, s); + break; + case DeserializeAs.DateTime: + s = columns[i].ToString().Trim(); + if (s.StartsWith('"') + && s.EndsWith('"')) { + s = s[1..^1]; + } + DateTime vDateTime; + if (_columnAttributes[i]?.DateFormat switch { + string dateFormat => DateTime.TryParseExact(s, dateFormat, null, DateTimeStyles.AssumeLocal, out vDateTime), + _ => DateTime.TryParse(s, null, DateTimeStyles.AssumeLocal, out vDateTime) + }) { + _properties[i].SetValue(item, vDateTime); + } else if (!_isNullable[i] || !string.IsNullOrWhiteSpace(s)) { + if (_columnAttributes[i]?.DateFormat is string dateFormat) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), $"Input string was not in correct DateTime format. Expected format was '{dateFormat}'."); + } else { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct DateTime format."); + } + } else { + _properties[i].SetValue(item, null); + } + break; + case DeserializeAs.Uri: + s = columns[i].ToString().Trim(); + if (s.StartsWith('"') + && s.EndsWith('"')) { + s = s[1..^1]; + } + s = s.Replace("\"\"", "\"").TrimEnd('\r'); + if (string.IsNullOrWhiteSpace(s)) { + _properties[i].SetValue(item, null); + } else { + _properties[i].SetValue(item, new Uri(s)); + } + break; + case DeserializeAs.Enum: + Type enumType; + if (_isNullable[i]) { + enumType = Nullable.GetUnderlyingType(_properties[i].PropertyType)!; + } else { + enumType = _properties[i].PropertyType; + } + if (Enum.TryParse(enumType, columns[i].ToString(), out object? vEnum) && vEnum != null) { + _properties[i].SetValue(item, vEnum); + } else if (!_isNullable[i] || columns[i].Length > 0) { + throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), $"Input string was not a valid {_properties[i].PropertyType.Name} value."); + } + break; + default: + throw new NotImplementedException(); + } + } + items.Add(item); + } + return items; + } + } + } + + """, + encoding: Encoding.UTF8 + ) + ); + } + } +} diff --git a/CsvSerializer/SerializerFactory.cs b/CsvSerializer/SerializerFactory.cs deleted file mode 100644 index 0a69442..0000000 --- a/CsvSerializer/SerializerFactory.cs +++ /dev/null @@ -1,761 +0,0 @@ -using Csv.Emitter; -using Csv.Internal.NaiveImpl; -using Csv.Internal.NativeImpl; -using Missil; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; - -namespace Csv { - internal static class SerializerFactory { - private static readonly Dictionary SERIALIZER_BY_TYPE = new Dictionary(); - private static readonly Dictionary DESERIALIZER_BY_TYPE = new Dictionary(); - - public static ISerializer GetOrCreateSerializer() where T : notnull { - if (SERIALIZER_BY_TYPE.TryGetValue(typeof(T), out ISerializer? serializer)) return serializer; - if (typeof(T).Name is string className - && (className.Length < 15 - || className[0] != '<' - || className[1] != '>') - && typeof(T).IsPublic) { - ImplEmitter implEmitter = new ImplEmitter($"Serializer{typeof(T).GUID.ToString("N")}"); - implEmitter.ImplementAction("SerializeHeader", gen => DefineSerializeHeader(gen)); - implEmitter.ImplementAction("SerializeItem", gen => DefineSerializeItem(gen)); - serializer = implEmitter.CreateInstance(); - SERIALIZER_BY_TYPE.Add(typeof(T), serializer); - } else { - serializer = new NaiveSerializer(); - } - return serializer; - } - - private static void DefineSerializeHeader(ILGenerator gen) { - _ = gen - .Ldarg_1() - .Do(gen => { - bool firstProperty = true; - foreach (PropertyInfo property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { - CsvColumnAttribute? columnAttribute = property.GetCustomAttribute(); - - if (!firstProperty) { - _ = gen - .Ldarg_2() - .Callvirt(Methods.StringBuilder_Append_Char); - } - - _ = gen - .Ldstr("\"" + (columnAttribute?.Name ?? property.Name).Replace("\"", "\"\"") + "\"") - .Callvirt(Methods.StringBuilder_Append_String); - - firstProperty = false; - } - }) - .Ldstr("\r\n") - .Callvirt(Methods.StringBuilder_Append_String) - .Pop() - .Ret(); - } - - private static void DefineSerializeItem(ILGenerator gen) { - PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); - _ = gen - - .DeclareLocal(typeof(string), out LocalBuilder? @string) - .DeclareLocal(typeof(DateTime), out LocalBuilder? @DateTime) - .DeclareLocal(typeof(DateTime?), out LocalBuilder? @NullableDateTime) - - .Ldarg_1() - .Do(emitter => { - bool firstProperty = true; - foreach (PropertyInfo property in properties) { - if (!property.CanRead) throw new CsvTypeException(typeof(T), property.Name, "Property doesn't have a public getter."); - CsvColumnAttribute? columnAttribute = property.GetCustomAttribute(); - - if (!firstProperty) { - _ = emitter - .Ldarg_3() - .Callvirt(Methods.StringBuilder_Append_Char); - } - _ = property.PropertyType switch - { - Type t => t switch - { - _ when t == typeof(bool) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_Boolean), - _ when Nullable.GetUnderlyingType(t) == typeof(bool) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(bool?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(byte) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_Byte), - _ when Nullable.GetUnderlyingType(t) == typeof(byte) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(byte?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(sbyte) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_SByte), - _ when Nullable.GetUnderlyingType(t) == typeof(sbyte) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(sbyte?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(short) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_Int16), - _ when Nullable.GetUnderlyingType(t) == typeof(short) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(short?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(ushort) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_UInt16), - _ when Nullable.GetUnderlyingType(t) == typeof(ushort) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(ushort?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(int) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_Int32), - _ when Nullable.GetUnderlyingType(t) == typeof(int) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(int?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(uint) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_UInt32), - _ when Nullable.GetUnderlyingType(t) == typeof(uint) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(uint?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(long) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_Int64), - _ when Nullable.GetUnderlyingType(t) == typeof(long) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(long?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(ulong) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_UInt64), - _ when Nullable.GetUnderlyingType(t) == typeof(ulong) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(ulong?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(float) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_Single), - _ when Nullable.GetUnderlyingType(t) == typeof(float) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(float?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(double) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_Double), - _ when Nullable.GetUnderlyingType(t) == typeof(double) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(double?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(decimal) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Callvirt(Methods.StringBuilder_Append_Decimal), - _ when Nullable.GetUnderlyingType(t) == typeof(decimal) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(typeof(decimal?)) - .Callvirt(Methods.StringBuilder_Append_Object), - _ when t == typeof(string) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Stloc(@string!.LocalIndex) - .Ldloc(@string!.LocalIndex) - .Brfalse(out Label @endif) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.StringBuilder_Append_Char) - .Ldloc(@string!.LocalIndex) - .Ldstr("\"") - .Ldstr("\"\"") - .Callvirt(Methods.String_Replace) - .Call(Methods.StringBuilder_Append_String) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.StringBuilder_Append_Char) - .Label(@endif), - _ when t == typeof(DateTime) => emitter - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.StringBuilder_Append_Char) - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Stloc(@DateTime!.LocalIndex) - .Ldloca_S(@DateTime!.LocalIndex) - .Do(gen => { - if (property.GetCustomAttribute()?.DateFormat is string dateFormat) { - gen - .Ldstr(dateFormat) - .Callvirt(Methods.DateTime_ToString_Format); - } else { - gen - .Callvirt(Methods.DateTime_ToString); - } - }) - .Callvirt(Methods.StringBuilder_Append_String) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.StringBuilder_Append_Char), - _ when Nullable.GetUnderlyingType(t) == typeof(DateTime) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Stloc(@NullableDateTime!.LocalIndex) - .Ldloca_S(@NullableDateTime!.LocalIndex) - .Callvirt(Methods.NullableDateTime_get_HasValue) - .Brfalse(out Label @endif) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.StringBuilder_Append_Char) - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Stloc(@NullableDateTime!.LocalIndex) - .Ldloca_S(@NullableDateTime!.LocalIndex) - .Callvirt(Methods.NullableDateTime_get_Value) - .Stloc(@DateTime!.LocalIndex) - .Ldloca_S(@DateTime!.LocalIndex) - .Do(gen => { - if (property.GetCustomAttribute()?.DateFormat is string dateFormat) { - gen - .Ldstr(dateFormat) - .Callvirt(Methods.DateTime_ToString_Format); - } else { - gen - .Callvirt(Methods.DateTime_ToString); - } - }) - .Callvirt(Methods.StringBuilder_Append_String) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.StringBuilder_Append_Char) - .Label(endif), - _ when t == typeof(Uri) => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Dup() - .Brfalse_S(out Label nullUri) - .Callvirt(Methods.Uri_ToString) - .Label(nullUri) - .Stloc(@string!.LocalIndex) - .Ldloc(@string!.LocalIndex) - .Brfalse(out Label @endif) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.StringBuilder_Append_Char) - .Ldloc(@string!.LocalIndex) - .Ldstr("\"") - .Ldstr("\"\"") - .Callvirt(Methods.String_Replace) - .Call(Methods.StringBuilder_Append_String) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.StringBuilder_Append_Char) - .Label(@endif), - _ when t.IsEnum => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(t) - .Call(Methods.StringBuilder_Append_Object), - _ when Nullable.GetUnderlyingType(t)?.IsEnum == true => emitter - .Ldarg_2() - .Callvirt(property.GetGetMethod()!) - .Box(t) - .Call(Methods.StringBuilder_Append_Object), - _ => throw new CsvTypeException(t) - } - }; - firstProperty = false; - } - }) - .Ldstr("\r\n") - .Callvirt(Methods.StringBuilder_Append_String) - .Pop() - .Ret(); - } - - public static IDeserializer GetOrCreateDeserializer() where T : notnull { - if (DESERIALIZER_BY_TYPE.TryGetValue(typeof(T), out IDeserializer? deserializer)) return deserializer; - //if (typeof(T).Name is string className - // && (className.Length < 15 - // || className[0] != '<' - // || className[1] != '>') - // && typeof(T).IsPublic) { - // ImplEmitter implEmitter = new ImplEmitter($"Deserializer{typeof(T).GUID.ToString("N")}"); - // implEmitter.ImplementFunc, ReadOnlyMemory, char, bool>("Deserialize", gen => DefineDeserialize(gen)); - // deserializer = implEmitter.CreateInstance(); - // DESERIALIZER_BY_TYPE.Add(typeof(T), deserializer); - //} else { - deserializer = new NaiveDeserializer(); - //} - return deserializer; - } - - private static void DefineDeserialize(ILGenerator gen) { - PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); - ConstructorInfo? constructorInfo = typeof(T).GetConstructor(Type.EmptyTypes); - bool isDefaultConstructor; - if (constructorInfo != null) { - isDefaultConstructor = true; - } else { - Type[] expectedConstructorSignature = properties.Select(prop => prop.PropertyType).ToArray(); - constructorInfo = typeof(T).GetConstructor(expectedConstructorSignature) ?? throw new CsvTypeException(typeof(T), "No suitable constructor found."); - isDefaultConstructor = false; - } - - // arg1: ReadOnlySpan csv - // arg2: char separator - // arg3: bool skipHeader - gen - .DeclareLocal(out LocalBuilder firstRow) - .DeclareLocal>(out LocalBuilder items) - .DeclareLocal>(out LocalBuilder columns) - .DeclareLocal(out LocalBuilder endOfLine) - .DeclareLocal(out LocalBuilder excMessage) - .DeclareLocal(out LocalBuilder obj) - .DeclareLocal(out LocalBuilder col) - .DeclareLocal(out LocalBuilder parseSuccess) - .DeclareLocal(out LocalBuilder isEmptyString) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(bool) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(bool)), out LocalBuilder? @bool) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(bool)), out LocalBuilder? nBool) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(byte) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(byte)), out LocalBuilder? @byte) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(byte)), out LocalBuilder? nByte) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(sbyte) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(sbyte)), out LocalBuilder? @sbyte) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(sbyte)), out LocalBuilder? nSbyte) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(short) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(short)), out LocalBuilder? @short) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(short)), out LocalBuilder? nShort) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(ushort) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(ushort)), out LocalBuilder? @ushort) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(ushort)), out LocalBuilder? nUshort) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(int) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(int)), out LocalBuilder? @int) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(int)), out LocalBuilder? nInt) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(uint) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(uint)), out LocalBuilder? @uint) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(uint)), out LocalBuilder? nUint) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(long) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(long)), out LocalBuilder? @long) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(long)), out LocalBuilder? nLong) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(ulong) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(ulong)), out LocalBuilder? @ulong) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(ulong)), out LocalBuilder? nUlong) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(float) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(float)), out LocalBuilder? @float) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(float)), out LocalBuilder? nFloat) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(double) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(double)), out LocalBuilder? @double) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(double)), out LocalBuilder? nDouble) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(decimal) || Nullable.GetUnderlyingType(prop.PropertyType) == typeof(decimal)), out LocalBuilder? @decimal) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(decimal)), out LocalBuilder? nDecimal) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(string)), out LocalBuilder? @string) - .DeclareLocalIf(properties.Any(prop => prop.PropertyType == typeof(DateTime)), out LocalBuilder? @DateTime) - .DeclareLocalIf(properties.Any(prop => Nullable.GetUnderlyingType(prop.PropertyType) == typeof(DateTime)), out LocalBuilder? nDateTime) - .Newobj(typeof(List).GetConstructor(Type.EmptyTypes)!) - .Stloc(items) - .Label(out Label beginLoop) - .Ldarg_1() // csv - .Callvirt(Methods.ReadOnlySpan_Char_get_Length) - .Brfalse(out Label endLoop) - .Ldarga_S(1) - .Ldarg_2() // separator - .Call(Methods.StringSplitter_ReadNextLine) - .Stloc(columns) - .Ldarg_3() // skipHeader - .Brfalse_S(out Label noSkipHeader) - .Ldloc(firstRow) - .Brfalse_S(noSkipHeader) - .Ldnull() - .Stloc(firstRow) - .Br_S(beginLoop) - .Label(noSkipHeader) - .Ldc_I4_X(properties.Length) - .Ldloc(columns) - .Callvirt(Methods.List_String_get_Count) - .Ceq() - .Brtrue_S(out Label validated) - .Ldarg_1() - .Ldc_I4_S((byte)'\n') - .Call(Methods.ReadOnlySpan_Char_IndexOf) - .Stloc(endOfLine) - .Ldloc(endOfLine) - .Ldc_I4_M1() - .Ceq() - .Brfalse_S(out Label splitSpan) - .Ldarg_1() // csv - .Callvirt(Methods.ReadOnlySpan_Char_ToString) - .Stloc(excMessage) - .Br_S(out Label throwExc) - .Label(splitSpan) - .Ldarg_1() // csv - .Ldc_I4_0() - .Ldloc(endOfLine) - .Call(Methods.ReadOnlySpan_Char_Slice) - .Callvirt(Methods.ReadOnlySpan_Char_ToString) - .Stloc(excMessage) - .Label(throwExc) - .Ldtoken() - .Ldloc(excMessage) - .Ldstr($"Row must consists of {properties.Length} columns.") - .Initobj() - .Throw() - .Label(validated) - .Newobj(constructorInfo) - .Stloc(obj) - .Ldloc(obj) - .Do(gen => { - for (int i = 0; i < properties.Length; i++) { - Type propertyType = properties[i].PropertyType; - LocalBuilder? local = null; - LocalBuilder? nullableLocal = null; - MethodInfo? tryParse = null; - string? formatExcMessage = null; - gen - .Ldloc(obj) - .Ldc_I4_X(i) - .Callvirt(Methods.List_String_get_Item) - .Stloc_S(col) - .Ldloc_S(col); - if (propertyType == typeof(bool)) { - (local, tryParse, formatExcMessage) = (@bool, Methods.Boolean_TryParse, "Input string was not in correct Boolean format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(bool)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@bool, nBool, Methods.Boolean_TryParse, "Input string was not in correct Boolean format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(byte)) { - (local, tryParse, formatExcMessage) = (@byte, Methods.Byte_TryParse, "Input string was not in correct byte format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(byte)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@byte, nByte, Methods.Byte_TryParse, "Input string was not in correct byte format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(sbyte)) { - (local, tryParse, formatExcMessage) = (@sbyte, Methods.SByte_TryParse, "Input string was not in correct sbyte format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(sbyte)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@sbyte, nSbyte, Methods.SByte_TryParse, "Input string was not in correct sbyte format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(short)) { - (local, tryParse, formatExcMessage) = (@short, Methods.Int16_TryParse, "Input string was not in correct Int16 format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(short)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@short, nShort, Methods.Int16_TryParse, "Input string was not in correct Int16 format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(ushort)) { - (local, tryParse, formatExcMessage) = (@ushort, Methods.UInt16_TryParse, "Input string was not in correct UInt16 format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(ushort)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@ushort, nUshort, Methods.UInt16_TryParse, "Input string was not in correct UInt16 format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(int)) { - (local, tryParse, formatExcMessage) = (@int, Methods.Int32_TryParse, "Input string was not in correct Int32 format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(int)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@int, nInt, Methods.Int32_TryParse, "Input string was not in correct Int32 format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(uint)) { - (local, tryParse, formatExcMessage) = (@uint, Methods.UInt32_TryParse, "Input string was not in correct UInt32 format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(uint)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@uint, nUint, Methods.UInt32_TryParse, "Input string was not in correct UInt32 format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(long)) { - (local, tryParse, formatExcMessage) = (@long, Methods.Int64_TryParse, "Input string was not in correct Int64 format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(long)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@long, nLong, Methods.Int64_TryParse, "Input string was not in correct Int64 format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(ulong)) { - (local, tryParse, formatExcMessage) = (@ulong, Methods.UInt64_TryParse, "Input string was not in correct UInt64 format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(ulong)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@ulong, nUlong, Methods.UInt64_TryParse, "Input string was not in correct UInt64 format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(float)) { - (local, tryParse, formatExcMessage) = (@float, Methods.Single_TryParse, "Input string was not in correct floating point format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(float)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@float, nFloat, Methods.Single_TryParse, "Input string was not in correct floating point format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(double)) { - (local, tryParse, formatExcMessage) = (@double, Methods.Double_TryParse, "Input string was not in correct floating point format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(double)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@double, nDouble, Methods.Double_TryParse, "Input string was not in correct floating point format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(decimal)) { - (local, tryParse, formatExcMessage) = (@decimal, Methods.UInt16_TryParse, "Input string was not in correct decimal format."); - goto EMIT_VALUE_PARSER; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(decimal)) { - (local, nullableLocal, tryParse, formatExcMessage) = (@decimal, nDecimal, Methods.UInt16_TryParse, "Input string was not in correct decimal format."); - goto EMIT_NULLABLE_VALUE_PARSER; - } else if (propertyType == typeof(string)) { - gen - .Callvirt(Methods.String_Trim) - .Stloc(@string!) - .Ldloc(@string!) - .Ldc_I4_S((int)'"') - .Callvirt(Methods.String_StartsWith) - .Brfalse_S(out Label notString) - .Ldloc(@string!) - .Ldc_I4_S((int)'"') - .Callvirt(Methods.String_EndsWith) - .Brfalse_S(notString) - .Ldloc(@string!) - .Ldc_I4_1() - .Ldloc(@string!) - .Callvirt(Methods.String_get_Length) - .Ldc_I4_2() - .Sub() - .Callvirt(Methods.String_Substring) - .Ldstr("\"\"") - .Ldstr("\"") - .Callvirt(Methods.String_Replace) - .Br_S(out Label end) - .Label(notString) - .Ldloc(@string!) - .Call(Methods.String_IsNullOrWhiteSpace) - .Brfalse_S(out Label invalidString) - .Ldnull() - .Br_S(end) - .Label(invalidString) - .Ldtoken() - .Ldstr(properties[i].Name) - .Ldloc_S(col) - .Newobj(Methods.CsvFormatException_ctor3) - .Throw() - .Label(end) - .Callvirt(properties[i].GetSetMethod()!); - continue; - } else if (propertyType == typeof(DateTime)) { - Label end = default; - gen - .Callvirt(Methods.String_Trim) - .Stloc(@string!) - .Ldloc(@string!) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.String_StartsWith) - .Brfalse_S(out Label invalidString) - .Ldloc(@string!) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.String_EndsWith) - .Brfalse_S(invalidString) - .Ldloc(@string!) - .Ldc_I4_1() - .Ldloc(@string!) - .Callvirt(Methods.String_get_Length) - .Ldc_I4_2() - .Sub() - .Callvirt(Methods.String_Substring) - .Ldstr("\"\"") - .Ldstr("\"") - .Callvirt(Methods.String_Replace) - .Stloc(@string!) - .Ldloc(@string!) - .Do(gen => { - CsvColumnAttribute? columnAttribute = properties[i].GetCustomAttribute(); - if (columnAttribute?.DateFormat is string dateFormat) { - gen - .Ldstr(dateFormat) - .Ldnull() - .Ldc_I4_X((int)DateTimeStyles.AssumeLocal) - .Ldloca_S(DateTime!) - .Call(Methods.DateTime_TryParseExact) - .Brfalse_S(out Label badFormat) - .Ldloc(DateTime!) - .Br_S(out end) - .Label(badFormat) - .Ldtoken() - .Ldstr(properties[i].Name) - .Ldloc_S(1) - .Ldstr($"Input string was not in correct DateTime format. Expected format was '{dateFormat}'.") - .Newobj(Methods.CsvFormatException_ctor4) - .Throw(); - } else { - gen - .Ldloca_S(DateTime!) - .Call(Methods.DateTime_TryParse) - .Brfalse_S(out Label badFormat) - .Ldloc(DateTime!) - .Br_S(out end) - .Label(badFormat) - .Ldtoken() - .Ldstr(properties[i].Name) - .Ldloc_S(1) - .Ldstr($"Input string was not in correct DateTime format.") - .Newobj(Methods.CsvFormatException_ctor4) - .Throw(); - } - }) - .Label(invalidString) - .Ldtoken() - .Ldstr(properties[i].Name) - .Ldloc_S(1) - .Newobj(Methods.CsvFormatException_ctor3) - .Throw() - .Label(end) - .Callvirt(properties[i].GetSetMethod()!); - continue; - } else if (Nullable.GetUnderlyingType(propertyType) == typeof(DateTime)) { - Label end = default; - gen - .Callvirt(Methods.String_Trim) - .Stloc(@string!) - .Ldloc(@string!) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.String_StartsWith) - .Brfalse_S(out Label invalidString) - .Ldloc(@string!) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.String_EndsWith) - .Brfalse_S(invalidString) - .Ldloc(@string!) - .Ldc_I4_S((byte)'"') - .Callvirt(Methods.String_EndsWith) - .Brfalse_S(invalidString) - .Ldloc(@string!) - .Ldc_I4_1() - .Ldloc(@string!) - .Callvirt(Methods.String_get_Length) - .Ldc_I4_2() - .Sub() - .Callvirt(Methods.String_Substring) - .Ldstr("\"\"") - .Ldstr("\"") - .Callvirt(Methods.String_Replace) - .Stloc(@string!) - .Ldloc(@string!) - .Do(gen => { - CsvColumnAttribute? columnAttribute = properties[i].GetCustomAttribute(); - if (columnAttribute?.DateFormat is string dateFormat) { - gen - .Ldstr(dateFormat) - .Ldnull() - .Ldc_I4_X((int)DateTimeStyles.AssumeLocal) - .Ldloca_S(DateTime!) - .Call(Methods.DateTime_TryParseExact) - .Brfalse_S(out Label badFormat) - .Ldloc(DateTime!) - .Br_S(out end) - .Label(badFormat) - .Ldtoken() - .Ldstr(properties[i].Name) - .Ldloc_S(col) - .Ldstr($"Input string was not in correct DateTime format. Expected format was '{dateFormat}'.") - .Newobj(Methods.CsvFormatException_ctor4) - .Throw(); - } else { - gen - .Ldloca_S(DateTime!) - .Call(Methods.DateTime_TryParse) - .Brfalse_S(out Label badFormat) - .Ldloc(DateTime!) - .Br_S(out end) - .Label(badFormat) - .Ldtoken() - .Ldstr(properties[i].Name) - .Ldloc_S(col) - .Ldstr($"Input string was not in correct DateTime format.") - .Newobj(Methods.CsvFormatException_ctor4) - .Throw(); - } - }) - .Label(invalidString) - .Ldloc(@string!) - .Call(Methods.String_IsNullOrWhiteSpace) - .Brfalse_S(out Label invalidToken) - .Ldloca_S(nDateTime!) - .Initobj() - .Ldloc_S(nDateTime!) - .Br_S(end) - .Label(invalidToken) - .Ldtoken() - .Ldstr(properties[i].Name) - .Ldloc_S(col) - .Newobj(Methods.CsvFormatException_ctor3) - .Throw() - .Label(end) - .Callvirt(properties[i].GetSetMethod()!); - continue; - } else { - throw new CsvPropertyTypeException(properties[i].PropertyType); - } - EMIT_VALUE_PARSER: - { - gen - .Ldloca_S(local!) - .Call(tryParse!) - .Stloc_S(parseSuccess) - .Ldloc_S(parseSuccess) - .Brfalse_S(out Label @else) - .Ldloc_S((byte)local!.LocalIndex) - .Br_S(out Label @endif) - .Label(@else) - .Ldtoken() - .Ldstr(properties[i].Name) - .Ldloc_S(col) - .Ldstr(formatExcMessage!) - .Newobj(Methods.CsvFormatException_ctor4) - .Throw() - .Label(@endif) - .Callvirt(properties[i].GetSetMethod()!); - continue; - } - EMIT_NULLABLE_VALUE_PARSER: - { - gen - .Ldloca_S(local!) - .Call(tryParse!) - .Stloc_S(parseSuccess) - .Ldloc_S(parseSuccess) - .Brfalse_S(out Label @elseif) - .Ldloc_S(local!) - .Newobj(properties[i].PropertyType.GetConstructor(new Type[] { local!.LocalType })!) - .Br_S(out Label @endif) - .Label(@elseif) - .Ldloc_S(col) - .Call(Methods.String_IsNullOrWhiteSpace) - .Stloc_S(isEmptyString) - .Ldloc_S(isEmptyString) - .Brfalse_S(out Label @else) - .Ldloca_S(nullableLocal!) - .Initobj(properties[i].PropertyType) - .Ldloc_S(nullableLocal!) - .Br_S(@endif) - .Label(@else) - .Ldtoken() - .Ldstr(properties[i].Name) - .Ldloc_S(col) - .Ldstr(formatExcMessage) - .Newobj(Methods.CsvFormatException_ctor4) - .Throw() - .Label(@endif) - .Callvirt(properties[i].GetSetMethod()!); - continue; - } - } - }) - .Callvirt(Methods.List_object_Add) - .Br(beginLoop) - .Label(endLoop) - .Ret(); - } - } -} diff --git a/CsvSerializer/SourceGenerator.cs b/CsvSerializer/SourceGenerator.cs new file mode 100644 index 0000000..aeb51e2 --- /dev/null +++ b/CsvSerializer/SourceGenerator.cs @@ -0,0 +1,34 @@ +using System.Diagnostics; +using Csv.Internals; +using Microsoft.CodeAnalysis; + +namespace CsvSerializer { + [Generator] + public class SourceGenerator : ISourceGenerator { + public void Initialize(GeneratorInitializationContext context) { +#if DEBUG + if (!Debugger.IsAttached) { + // Uncomment following line to debug the generator + // Debugger.Launch(); + } +#endif + context.RegisterForPostInitialization(context => { + context.AddCsvSerializer(); + context.AddAttributes(); + context.AddExceptions(); + context.AddISerializer(); + context.AddIDeserializer(); + context.AddIConverter(); + context.AddStringSplitter(); + context.AddNaiveSerializer(); + context.AddNaiveDeserializer(); + }); + + context.RegisterForSyntaxNotifications(() => new SerializeOrDeserializeSyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext context) { + context.AddNativeImplementations(); + } + } +} diff --git a/Tests/ConverterTests/DateTimeConverterTests.cs b/Tests/ConverterTests/DateTimeConverterTests.cs deleted file mode 100644 index f38bb91..0000000 --- a/Tests/ConverterTests/DateTimeConverterTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Csv; -using Csv.Internal.Converters; -using FluentAssertions; -using Missil; -using System; -using System.Globalization; -using System.Reflection.Emit; -using System.Text; -using Xunit; - -namespace Tests.ConverterTests { - public class DateTimeConverterTests { - [Fact] - public void DateTimeSerializerIsValid() { - DateTimeConverter converter = new DateTimeConverter(); - DateTime date = new DateTime(2019, 5, 4, 3, 2, 1); - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, date, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("05/04/2019 03:02:01"); - } - - [Fact] - public void CanSerializeUsingCustomDateFormat() { - DateTimeConverter converter = new DateTimeConverter(); - DateTime date = new DateTime(2019, 5, 4, 3, 2, 1); - StringBuilder stringBuilder = new StringBuilder(); - CsvColumnAttribute attribute = new CsvColumnAttribute("Date") { DateFormat = "yyyy/MMM/dd H:mm:ss" }; - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, date, attribute, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("2019/May/04 3:02:01"); - } - - [Fact] - public void DateTimeDeserializerIsValid() { - DateTimeConverter converter = new DateTimeConverter(); - string text = "05/04/2019 03:02:01"; - DateTime date = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - date.Should().Be(new DateTime(2019, 5, 4, 3, 2, 1)); - } - - [Fact] - public void CanDeserializeUsingCustomDateFormat() { - DateTimeConverter converter = new DateTimeConverter(); - string text = "2019/May/04 3:02:01"; - CsvColumnAttribute attribute = new CsvColumnAttribute("Date") { DateFormat = "yyyy/MMM/dd H:mm:ss" }; - DateTime date = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, attribute); - date.Should().Be(new DateTime(2019, 5, 4, 3, 2, 1)); - } - - [Fact] - public void EmittedSerializerIsValid() { - DateTimeConverter converter = new DateTimeConverter(); - DateTime date = new DateTime(2019, 5, 4, 3, 2, 1); - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(DateTime), typeof(IFormatProvider), typeof(char) }, typeof(DateTimeConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, local, null, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { date, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("05/04/2019 03:02:01"); - } - - [Fact] - public void CanEmitSerializerWithCustomDateFormat() { - DateTimeConverter converter = new DateTimeConverter(); - DateTime date = new DateTime(2019, 5, 4, 3, 2, 1); - CsvColumnAttribute attribute = new CsvColumnAttribute("Date") { DateFormat = "yyyy/MMM/dd H:mm:ss" }; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(DateTime), typeof(IFormatProvider), typeof(char) }, typeof(DateTimeConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, local, null, attribute)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { date, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("2019/May/04 3:02:01"); - } - - [Fact] - public void EmittedDeserializerIsValid() { - DateTimeConverter converter = new DateTimeConverter(); - string text = "05/04/2019 03:02:01"; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(DateTime), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(DateTimeConverterTests)); - deserialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, local, null, null)) - .Ret(); - object deserialized = deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - deserialized.Should().Be(new DateTime(2019, 5, 4, 3, 2, 1)); - } - - [Fact] - public void CanEmitDeserializerWithCustomDateFormat() { - DateTimeConverter converter = new DateTimeConverter(); - string text = "2019/May/04 3:02:01"; - CsvColumnAttribute attribute = new CsvColumnAttribute("Date") { DateFormat = "yyyy/MMM/dd H:mm:ss" }; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(DateTime), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(DateTimeConverterTests)); - deserialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, local, null, attribute)) - .Ret(); - object deserialized = deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - deserialized.Should().Be(new DateTime(2019, 5, 4, 3, 2, 1)); - } - } -} diff --git a/Tests/ConverterTests/EnumConverterTests.cs b/Tests/ConverterTests/EnumConverterTests.cs deleted file mode 100644 index c5b605f..0000000 --- a/Tests/ConverterTests/EnumConverterTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Csv.Internal.Converters; -using FluentAssertions; -using Missil; -using System; -using System.Globalization; -using System.Net; -using System.Reflection.Emit; -using System.Text; -using Xunit; - -namespace Tests.ConverterTests { - public class EnumConverterTests { - [Fact] - public void EnumSerializerIsValid() { - EnumConverter converter = new EnumConverter(); - HttpStatusCode code = HttpStatusCode.NotFound; - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, code, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("NotFound"); - } - - [Fact] - public void EnumDeserializerIsValid() { - EnumConverter converter = new EnumConverter(); - string text = "NotFound"; - HttpStatusCode code = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - code.Should().Be(HttpStatusCode.NotFound); - } - - [Fact] - public void EmittedSerializerIsValid() { - EnumConverter converter = new EnumConverter(); - HttpStatusCode code = HttpStatusCode.NotFound; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(HttpStatusCode), typeof(IFormatProvider), typeof(char) }, typeof(EnumConverterTests)); - serialize.GetILGenerator() - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, null, null, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { code, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("NotFound"); - } - - [Fact] - public void EmittedDeserializerIsValid() { - EnumConverter converter = new EnumConverter(); - string text = "NotFound"; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(HttpStatusCode), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(UriConverterTests)); - deserialize.GetILGenerator() - .DeclareLocal(out LocalBuilder secondaryLocal) - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, null, secondaryLocal, null)) - .Ret(); - HttpStatusCode deserialized = (HttpStatusCode)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - deserialized.Should().Be(HttpStatusCode.NotFound); - } - } -} diff --git a/Tests/ConverterTests/NullableDateTimeConverterTests.cs b/Tests/ConverterTests/NullableDateTimeConverterTests.cs deleted file mode 100644 index f8928d3..0000000 --- a/Tests/ConverterTests/NullableDateTimeConverterTests.cs +++ /dev/null @@ -1,167 +0,0 @@ -using Csv; -using Csv.Internal.Converters; -using FluentAssertions; -using Missil; -using System; -using System.Globalization; -using System.Reflection.Emit; -using System.Text; -using Xunit; - -namespace Tests.ConverterTests { - public class NullableDateTimeConverterTests { - [Fact] - public void DateTimeSerializerIsValid() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - DateTime? date = new DateTime(2019, 5, 4, 3, 2, 1); - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, date, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("05/04/2019 03:02:01"); - } - - [Fact] - public void NullIsSerializedIntoEmptyString() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - DateTime? date = null; - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, date, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be(""); - } - - [Fact] - public void CanSerializeUsingCustomDateFormat() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - DateTime? date = new DateTime(2019, 5, 4, 3, 2, 1); - StringBuilder stringBuilder = new StringBuilder(); - CsvColumnAttribute attribute = new CsvColumnAttribute("Date") { DateFormat = "yyyy/MMM/dd H:mm:ss" }; - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, date, attribute, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("2019/May/04 3:02:01"); - } - - [Fact] - public void DateTimeDeserializerIsValid() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - string text = "05/04/2019 03:02:01"; - DateTime? date = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - date.Should().Be(new DateTime(2019, 5, 4, 3, 2, 1)); - } - - [Fact] - public void EmptyStringIsDeserializedIntoNull() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - string text = ""; - DateTime? date = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - date.Should().Be(null); - } - - [Fact] - public void CanDeserializeUsingCustomDateFormat() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - string text = "2019/May/04 3:02:01"; - CsvColumnAttribute attribute = new CsvColumnAttribute("Date") { DateFormat = "yyyy/MMM/dd H:mm:ss" }; - DateTime? date = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, attribute); - date.Should().Be(new DateTime(2019, 5, 4, 3, 2, 1)); - } - - [Fact] - public void EmittedSerializerIsValid() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - DateTime? date = new DateTime(2019, 5, 4, 3, 2, 1); - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(DateTime?), typeof(IFormatProvider), typeof(char) }, typeof(DateTimeConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, nullableLocal, local, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { date, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("05/04/2019 03:02:01"); - } - - [Fact] - public void EmitterSerializerSerializesNullIntoEmptyString() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - DateTime? date = null; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(DateTime?), typeof(IFormatProvider), typeof(char) }, typeof(DateTimeConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, nullableLocal, local, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { date, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be(""); - } - - [Fact] - public void CanEmitSerializerWithCustomDateFormat() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - DateTime? date = new DateTime(2019, 5, 4, 3, 2, 1); - CsvColumnAttribute attribute = new CsvColumnAttribute("Date") { DateFormat = "yyyy/MMM/dd H:mm:ss" }; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(DateTime?), typeof(IFormatProvider), typeof(char) }, typeof(DateTimeConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, nullableLocal, local, attribute)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { date, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("2019/May/04 3:02:01"); - } - - [Fact] - public void EmittedDeserializerIsValid() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - string text = "05/04/2019 03:02:01"; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(DateTime?), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(DateTimeConverterTests)); - deserialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .DeclareLocal(out LocalBuilder local) - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, nullableLocal, local, null)) - .Ret(); - DateTime? deserialized = (DateTime?)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - deserialized.Should().Be(new DateTime(2019, 5, 4, 3, 2, 1)); - } - - [Fact] - public void EmittedDeserializerDeserializesEmptyStringIntoNull() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - string text = ""; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(DateTime?), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(DateTimeConverterTests)); - deserialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .DeclareLocal(out LocalBuilder local) - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, nullableLocal, local, null)) - .Ret(); - DateTime? deserialized = (DateTime?)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - deserialized.Should().Be(null); - } - - [Fact] - public void CanEmitDeserializerWithCustomDateFormat() { - NullableDateTimeConverter converter = new NullableDateTimeConverter(); - string text = "2019/May/04 3:02:01"; - CsvColumnAttribute attribute = new CsvColumnAttribute("Date") { DateFormat = "yyyy/MMM/dd H:mm:ss" }; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(DateTime?), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(DateTimeConverterTests)); - deserialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .DeclareLocal(out LocalBuilder local) - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, nullableLocal, local, attribute)) - .Ret(); - DateTime? deserialized = (DateTime?)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - deserialized.Should().Be(new DateTime(2019, 5, 4, 3, 2, 1)); - } - } -} diff --git a/Tests/ConverterTests/NullableEnumConverterTests.cs b/Tests/ConverterTests/NullableEnumConverterTests.cs deleted file mode 100644 index 9d24f3f..0000000 --- a/Tests/ConverterTests/NullableEnumConverterTests.cs +++ /dev/null @@ -1,111 +0,0 @@ -using Csv.Internal.Converters; -using FluentAssertions; -using Missil; -using System; -using System.Globalization; -using System.Net; -using System.Reflection.Emit; -using System.Text; -using Xunit; - -namespace Tests.ConverterTests { - public class NullableEnumConverterTests { - [Fact] - public void NullableEnumSerializerIsValid() { - NullableEnumConverter converter = new NullableEnumConverter(); - HttpStatusCode? code = HttpStatusCode.NotFound; - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, code, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("NotFound"); - } - - [Fact] - public void NullValueIsSerializedIntoEmptyString() { - NullableEnumConverter converter = new NullableEnumConverter(); - HttpStatusCode? code = null; - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, code, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be(""); - } - - [Fact] - public void NullableEnumDeserializerIsValid() { - NullableEnumConverter converter = new NullableEnumConverter(); - string text = "NotFound"; - HttpStatusCode? code = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - code.Should().Be(HttpStatusCode.NotFound); - } - - [Fact] - public void EmptyStringIsDeserializedIntoNullValue() { - NullableEnumConverter converter = new NullableEnumConverter(); - string text = ""; - HttpStatusCode? code = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - code.Should().BeNull(); - } - - [Fact] - public void EmittedSerializerIsValid() { - NullableEnumConverter converter = new NullableEnumConverter(); - HttpStatusCode? code = HttpStatusCode.NotFound; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(HttpStatusCode?), typeof(IFormatProvider), typeof(char) }, typeof(NullableEnumConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, nullableLocal, null, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { code, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("NotFound"); - } - - [Fact] - public void EmittedSerializerSerializesNullValueIntoEmptyString() { - NullableEnumConverter converter = new NullableEnumConverter(); - HttpStatusCode? code = null; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(HttpStatusCode?), typeof(IFormatProvider), typeof(char) }, typeof(NullableEnumConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, nullableLocal, null, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { code, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be(""); - } - - [Fact] - public void EmittedDeserializerIsValid() { - NullableEnumConverter converter = new NullableEnumConverter(); - string text = "NotFound"; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(HttpStatusCode?), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(NullableEnumConverterTests)); - deserialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .DeclareLocal(out LocalBuilder secondaryLocal) - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, nullableLocal, secondaryLocal, null)) - .Ret(); - HttpStatusCode? code = (HttpStatusCode?)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - code.Should().Be(HttpStatusCode.NotFound); - } - - [Fact] - public void EmittedDeserializerDeserializesEmptyStringIntoNull() { - NullableEnumConverter converter = new NullableEnumConverter(); - string text = ""; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(HttpStatusCode?), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(NullableEnumConverterTests)); - deserialize.GetILGenerator() - .DeclareLocal(out LocalBuilder nullableLocal) - .DeclareLocal(out LocalBuilder secondaryLocal) - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, nullableLocal, secondaryLocal, null)) - .Ret(); - HttpStatusCode? code = (HttpStatusCode?)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - code.Should().BeNull(); - } - } -} diff --git a/Tests/ConverterTests/NumberConverterTests.cs b/Tests/ConverterTests/NumberConverterTests.cs deleted file mode 100644 index 32a2984..0000000 --- a/Tests/ConverterTests/NumberConverterTests.cs +++ /dev/null @@ -1,220 +0,0 @@ -using Csv; -using Csv.Internal; -using Csv.Internal.Converters; -using FluentAssertions; -using Missil; -using System; -using System.Globalization; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; -using Xunit; - -namespace Tests.ConverterTests { - public class NumberConverterTests { - [Theory] - [InlineData(typeof(ByteConverter), typeof(byte), (byte)0x88, "136")] - [InlineData(typeof(ByteConverter), typeof(byte), (byte)0xff, "255")] - [InlineData(typeof(NullableByteConverter), typeof(byte?), (byte)0x88, "136")] - [InlineData(typeof(NullableByteConverter), typeof(byte?), (byte)0xff, "255")] - [InlineData(typeof(NullableByteConverter), typeof(byte?), null, "")] - [InlineData(typeof(SByteConverter), typeof(sbyte), (sbyte)-128, "-128")] - [InlineData(typeof(SByteConverter), typeof(sbyte), (sbyte)127, "127")] - [InlineData(typeof(Int16Converter), typeof(short), (short)32000, "32000")] - [InlineData(typeof(Int16Converter), typeof(short), (short)-32000, "-32000")] - [InlineData(typeof(Int64Converter), typeof(long), 9223372036854775807L, "9223372036854775807")] - [InlineData(typeof(Int64Converter), typeof(long), -9223372036854775808L, "-9223372036854775808")] - [InlineData(typeof(NullableInt64Converter), typeof(long?), 9223372036854775807L, "9223372036854775807")] - [InlineData(typeof(NullableInt64Converter), typeof(long?), -9223372036854775808L, "-9223372036854775808")] - [InlineData(typeof(NullableInt64Converter), typeof(long?), null, "")] - [InlineData(typeof(SingleConverter), typeof(float), 123.45f, "123.45")] - [InlineData(typeof(SingleConverter), typeof(float), -123.45f, "-123.45")] - [InlineData(typeof(DoubleConverter), typeof(double), 123.45, "123.45")] - [InlineData(typeof(DoubleConverter), typeof(double), -123.45, "-123.45")] - [InlineData(typeof(NullableDoubleConverter), typeof(double?), 123.45, "123.45")] - [InlineData(typeof(NullableDoubleConverter), typeof(double?), -123.45, "-123.45")] - [InlineData(typeof(NullableDoubleConverter), typeof(double?), null, "")] - [InlineData(typeof(DecimalConverter), typeof(decimal), 123.45, "123.45")] - [InlineData(typeof(DecimalConverter), typeof(decimal), -123.45, "-123.45")] - [InlineData(typeof(NullableDecimalConverter), typeof(decimal?), 123.45, "123.45")] - [InlineData(typeof(NullableDecimalConverter), typeof(decimal?), -123.45, "-123.45")] - [InlineData(typeof(NullableDecimalConverter), typeof(decimal?), null, "")] - [InlineData(typeof(BooleanConverter), typeof(bool), true, "True")] - [InlineData(typeof(BooleanConverter), typeof(bool), false, "False")] - [InlineData(typeof(NullableBooleanConverter), typeof(bool?), true, "True")] - [InlineData(typeof(NullableBooleanConverter), typeof(bool?), false, "False")] - [InlineData(typeof(NullableBooleanConverter), typeof(bool?), null, "")] - public void AppendToStringBuilderIsValid(Type converterType, Type valueType, object testValue, string expectedResult) { - if (valueType == typeof(decimal)) { - testValue = (decimal)(double)testValue; - } else if (valueType == typeof(decimal?)) { - testValue = (decimal?)(double?)testValue; - } - object converterObj = Activator.CreateInstance(converterType)!; - MethodInfo serialize = converterType.GetMethod("AppendToStringBuilder", new Type[] { typeof(StringBuilder), typeof(IFormatProvider), valueType, typeof(CsvColumnAttribute), typeof(char) })!; - StringBuilder stringBuilder = new StringBuilder(); - serialize.Invoke(converterObj, new[] { stringBuilder, CultureInfo.InvariantCulture, testValue, null, ',' }); - string serialized = stringBuilder.ToString(); - serialized.Should().Be(expectedResult); - } - - [Theory] - [InlineData(typeof(ByteConverter), "136", (byte)0x88)] - [InlineData(typeof(ByteConverter), "255", (byte)0xff)] - [InlineData(typeof(NullableByteConverter), "136", (byte)0x88)] - [InlineData(typeof(NullableByteConverter), "255", (byte)0xff)] - [InlineData(typeof(NullableByteConverter), "", null)] - [InlineData(typeof(SByteConverter), "-128", (sbyte)-128)] - [InlineData(typeof(SByteConverter), "127", (sbyte)127)] - [InlineData(typeof(Int16Converter), "32000", (short)32000)] - [InlineData(typeof(Int16Converter), "-32000", (short)-32000)] - [InlineData(typeof(Int64Converter), "9223372036854775807", 9223372036854775807L)] - [InlineData(typeof(Int64Converter), "-9223372036854775808", -9223372036854775808L)] - [InlineData(typeof(NullableInt64Converter), "9223372036854775807", 9223372036854775807L)] - [InlineData(typeof(NullableInt64Converter), "-9223372036854775808", -9223372036854775808L)] - [InlineData(typeof(NullableInt64Converter), "", null)] - [InlineData(typeof(SingleConverter), "123.45", 123.45f)] - [InlineData(typeof(SingleConverter), "-123.45", -123.45f)] - [InlineData(typeof(DoubleConverter), "123.45", 123.45)] - [InlineData(typeof(DoubleConverter), "-123.45", -123.45)] - [InlineData(typeof(NullableDoubleConverter), "123.45", 123.45)] - [InlineData(typeof(NullableDoubleConverter), "-123.45", -123.45)] - [InlineData(typeof(NullableDoubleConverter), "", null)] - [InlineData(typeof(DecimalConverter), "123.45", 123.45)] - [InlineData(typeof(DecimalConverter), "-123.45", -123.45)] - [InlineData(typeof(NullableDecimalConverter), "123.45", 123.45)] - [InlineData(typeof(NullableDecimalConverter), "-123.45", -123.45)] - [InlineData(typeof(NullableDecimalConverter), "", null)] - [InlineData(typeof(NullableBooleanConverter), "True", true)] - [InlineData(typeof(NullableBooleanConverter), "False", false)] - [InlineData(typeof(NullableBooleanConverter), "", null)] - public void DeserializeIsValid(Type converterType, string testLiteral, object expectedResult) { - if (converterType == typeof(DecimalConverter)) { - expectedResult = (decimal)(double)expectedResult; - } else if (converterType == typeof(NullableDecimalConverter)) { - expectedResult = (decimal?)(double?)expectedResult; - } - object converterObj = Activator.CreateInstance(converterType)!; - MethodInfo deserialize = converterType.GetMethod("Deserialize", new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider), typeof(CsvColumnAttribute) })!; - object deserialized = deserialize.Invoke(converterObj, new object?[] { testLiteral.AsMemory(), CultureInfo.InvariantCulture, null })!; - deserialized.Should().Be(expectedResult); - } - - [Theory] - [InlineData(typeof(ByteConverter), typeof(byte), (byte)0x88, "136")] - [InlineData(typeof(ByteConverter), typeof(byte), (byte)0xff, "255")] - [InlineData(typeof(NullableByteConverter), typeof(byte?), (byte)0x88, "136")] - [InlineData(typeof(NullableByteConverter), typeof(byte?), (byte)0xff, "255")] - [InlineData(typeof(NullableByteConverter), typeof(byte?), null, "")] - [InlineData(typeof(SByteConverter), typeof(sbyte), (sbyte)-128, "-128")] - [InlineData(typeof(SByteConverter), typeof(sbyte), (sbyte)127, "127")] - [InlineData(typeof(Int16Converter), typeof(short), (short)32000, "32000")] - [InlineData(typeof(Int16Converter), typeof(short), (short)-32000, "-32000")] - [InlineData(typeof(Int64Converter), typeof(long), 9223372036854775807L, "9223372036854775807")] - [InlineData(typeof(Int64Converter), typeof(long), -9223372036854775808L, "-9223372036854775808")] - [InlineData(typeof(NullableInt64Converter), typeof(long?), 9223372036854775807L, "9223372036854775807")] - [InlineData(typeof(NullableInt64Converter), typeof(long?), -9223372036854775808L, "-9223372036854775808")] - [InlineData(typeof(NullableInt64Converter), typeof(long?), null, "")] - [InlineData(typeof(SingleConverter), typeof(float), 123.45f, "123.45")] - [InlineData(typeof(SingleConverter), typeof(float), -123.45f, "-123.45")] - [InlineData(typeof(DoubleConverter), typeof(double), 123.45, "123.45")] - [InlineData(typeof(DoubleConverter), typeof(double), -123.45, "-123.45")] - [InlineData(typeof(NullableDoubleConverter), typeof(double?), 123.45, "123.45")] - [InlineData(typeof(NullableDoubleConverter), typeof(double?), -123.45, "-123.45")] - [InlineData(typeof(NullableDoubleConverter), typeof(double?), null, "")] - [InlineData(typeof(DecimalConverter), typeof(decimal), 123.45, "123.45")] - [InlineData(typeof(DecimalConverter), typeof(decimal), -123.45, "-123.45")] - [InlineData(typeof(NullableDecimalConverter), typeof(decimal?), 123.45, "123.45")] - [InlineData(typeof(NullableDecimalConverter), typeof(decimal?), -123.45, "-123.45")] - [InlineData(typeof(NullableDecimalConverter), typeof(decimal?), null, "")] - [InlineData(typeof(BooleanConverter), typeof(bool), true, "True")] - [InlineData(typeof(BooleanConverter), typeof(bool), false, "False")] - [InlineData(typeof(NullableBooleanConverter), typeof(bool?), true, "True")] - [InlineData(typeof(NullableBooleanConverter), typeof(bool?), false, "False")] - [InlineData(typeof(NullableBooleanConverter), typeof(bool?), null, "")] - public void EmittedAppendToStringBuilderIsValid(Type emitterType, Type valueType, object testValue, string expectedResult) { - if (valueType == typeof(decimal)) { - testValue = (decimal)(double)testValue; - } else if (valueType == typeof(decimal?)) { - testValue = (decimal?)(double?)testValue; - } - IConverterEmitter emitter = (IConverterEmitter)Activator.CreateInstance(emitterType)!; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { valueType, typeof(IFormatProvider), typeof(char) }, typeof(NumberConverterTests)); - LocalBuilder? local = null; - LocalBuilder? secondaryLocal = null; - serialize.GetILGenerator() - .Emit(gen => { - if (valueType == typeof(float) || valueType == typeof(double) || valueType == typeof(decimal)) { - gen.DeclareLocal(valueType, out local); - } else if (Nullable.GetUnderlyingType(valueType) is Type underlyingType) { - if (underlyingType == typeof(float) || underlyingType == typeof(double) || underlyingType == typeof(decimal)) { - gen.DeclareLocal(typeof(Nullable<>).MakeGenericType(underlyingType), out local); - gen.DeclareLocal(valueType, out secondaryLocal); - } else { - gen.DeclareLocal(typeof(Nullable<>).MakeGenericType(underlyingType), out local); - } - } - }) - .Newobj() - .Ldarg_0() - .Emit(gen => emitter.EmitAppendToStringBuilder(gen, local, secondaryLocal, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new[] { testValue, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be(expectedResult); - } - - [Theory] - [InlineData(typeof(ByteConverter), typeof(byte), "136", (byte)0x88)] - [InlineData(typeof(ByteConverter), typeof(byte), "255", (byte)0xff)] - [InlineData(typeof(NullableByteConverter), typeof(byte?), "136", (byte)0x88)] - [InlineData(typeof(NullableByteConverter), typeof(byte?), "255", (byte)0xff)] - [InlineData(typeof(NullableByteConverter), typeof(byte?), "", null)] - [InlineData(typeof(SByteConverter), typeof(sbyte), "-128", (sbyte)-128)] - [InlineData(typeof(SByteConverter), typeof(sbyte), "127", (sbyte)127)] - [InlineData(typeof(Int16Converter), typeof(short), "32000", (short)32000)] - [InlineData(typeof(Int16Converter), typeof(short), "-32000", (short)-32000)] - [InlineData(typeof(Int64Converter), typeof(long), "9223372036854775807", 9223372036854775807L)] - [InlineData(typeof(Int64Converter), typeof(long), "-9223372036854775808", -9223372036854775808L)] - [InlineData(typeof(NullableInt64Converter), typeof(long?), "9223372036854775807", 9223372036854775807L)] - [InlineData(typeof(NullableInt64Converter), typeof(long?), "-9223372036854775808", -9223372036854775808L)] - [InlineData(typeof(NullableInt64Converter), typeof(long?), "", null)] - [InlineData(typeof(SingleConverter), typeof(float), "123.45", 123.45f)] - [InlineData(typeof(SingleConverter), typeof(float), "-123.45", -123.45f)] - [InlineData(typeof(DoubleConverter), typeof(double), "123.45", 123.45)] - [InlineData(typeof(DoubleConverter), typeof(double), "-123.45", -123.45)] - [InlineData(typeof(NullableDoubleConverter), typeof(double?), "123.45", 123.45)] - [InlineData(typeof(NullableDoubleConverter), typeof(double?), "-123.45", -123.45)] - [InlineData(typeof(NullableDoubleConverter), typeof(double?), "", null)] - [InlineData(typeof(DecimalConverter), typeof(decimal), "123.45", 123.45)] - [InlineData(typeof(DecimalConverter), typeof(decimal), "-123.45", -123.45)] - [InlineData(typeof(NullableDecimalConverter), typeof(decimal?), "123.45", 123.45)] - [InlineData(typeof(NullableDecimalConverter), typeof(decimal?), "-123.45", -123.45)] - [InlineData(typeof(NullableDecimalConverter), typeof(decimal?), "", null)] - [InlineData(typeof(BooleanConverter), typeof(bool), "True", true)] - [InlineData(typeof(BooleanConverter), typeof(bool), "False", false)] - [InlineData(typeof(NullableBooleanConverter), typeof(bool?), "True", true)] - [InlineData(typeof(NullableBooleanConverter), typeof(bool?), "False", false)] - [InlineData(typeof(NullableBooleanConverter), typeof(bool?), "", null)] - public void EmittedDeserializeIsValid(Type emitterType, Type valueType, string testLiteral, object expectedResult) { - if (valueType == typeof(decimal)) { - expectedResult = (decimal)(double)expectedResult; - } else if (valueType == typeof(decimal?)) { - expectedResult = (decimal?)(double?)expectedResult; - } - IConverterEmitter emitter = (IConverterEmitter)Activator.CreateInstance(emitterType)!; - DynamicMethod deserialize = new DynamicMethod("Deserialize", valueType, new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(NumberConverterTests)); - LocalBuilder? local = null; - deserialize.GetILGenerator() - .Emit(gen => Nullable.GetUnderlyingType(valueType) switch { - { } => gen.DeclareLocal(valueType, out local), - _ => gen - }) - .Ldarga_S(0) - .Emit(gen => emitter.EmitDeserialize(gen, local, null, null)) - .Ret(); - object deserialized = deserialize.Invoke(this, new object?[] { testLiteral.AsMemory(), CultureInfo.InvariantCulture })!; - deserialized.Should().Be(expectedResult); - } - } -} diff --git a/Tests/ConverterTests/StringConverterTests.cs b/Tests/ConverterTests/StringConverterTests.cs deleted file mode 100644 index a26900e..0000000 --- a/Tests/ConverterTests/StringConverterTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Csv.Internal.Converters; -using FluentAssertions; -using System; -using System.Globalization; -using System.Reflection.Emit; -using System.Text; -using Xunit; -using Missil; - -namespace Tests.ConverterTests { - public class StringConverterTests { - [Fact] - public void StringSerializerIsValid() { - StringConverter converter = new StringConverter(); - string s = "Hello \" world"; - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, s, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("\"Hello \"\" world\""); - } - - [Fact] - public void NullStringIsSerializedIntoEmptyString() { - StringConverter converter = new StringConverter(); - string? s = null; - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, s, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be(""); - } - - [Fact] - public void EmptyStringIsSerializedIntoEmptyQuotedString() { - StringConverter converter = new StringConverter(); - string s = string.Empty; - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, s, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("\"\""); - } - - [Fact] - public void StringDeserializerIsValid() { - StringConverter converter = new StringConverter(); - string text = "Hello \" world"; - string? deserialized = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - deserialized.Should().Be("Hello \" world"); - } - - [Fact] - public void EmptyStringIsDeserializedIntoEmptyString() { - StringConverter converter = new StringConverter(); - string text = ""; - string? deserialized = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - deserialized.Should().Be(""); - } - - [Fact] - public void EmittedSerializerIsValid() { - StringConverter converter = new StringConverter(); - string s = "Hello \" world"; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(string), typeof(IFormatProvider), typeof(char) }, typeof(StringConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, local, null, null)) - .Callvirt("ToString") - .Ret(); - string? serialized = (string?)serialize.Invoke(null, new object?[] { s, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("\"Hello \"\" world\""); - } - - [Fact] - public void EmittedSerializerSerializesNullIntoEmptyString() { - StringConverter converter = new StringConverter(); - string? s = null; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(string), typeof(IFormatProvider), typeof(char) }, typeof(StringConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, local, null, null)) - .Callvirt("ToString") - .Ret(); - string? serialized = (string?)serialize.Invoke(null, new object?[] { s, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be(""); - } - - [Fact] - public void EmittedSerializerSerializesEmptyStringIntoEmptyQuotedString() { - StringConverter converter = new StringConverter(); - string s = string.Empty; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(string), typeof(IFormatProvider), typeof(char) }, typeof(StringConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, local, null, null)) - .Callvirt("ToString") - .Ret(); - string? serialized = (string?)serialize.Invoke(null, new object?[] { s, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("\"\""); - } - - [Fact] - public void EmittedDeserializerIsValid() { - StringConverter converter = new StringConverter(); - string text = "Hello \" world"; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(string), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider), typeof(char) }, typeof(StringConverterTests)); - deserialize.GetILGenerator() - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, null, null, null)) - .Ret(); - string? deserialized = (string?)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture, ',' })!; - deserialized.Should().Be("Hello \" world"); - } - - [Fact] - public void EmittedDeserializerDeserializesEmptyStringIntoEmptyString() { - StringConverter converter = new StringConverter(); - string text = ""; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(string), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider), typeof(char) }, typeof(StringConverterTests)); - deserialize.GetILGenerator() - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, null, null, null)) - .Ret(); - string? deserialized = (string?)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture, ',' })!; - deserialized.Should().Be(""); - } - } -} diff --git a/Tests/ConverterTests/UriConverterTests.cs b/Tests/ConverterTests/UriConverterTests.cs deleted file mode 100644 index 7bde800..0000000 --- a/Tests/ConverterTests/UriConverterTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Csv.Internal.Converters; -using FluentAssertions; -using Missil; -using System; -using System.Globalization; -using System.Reflection.Emit; -using System.Text; -using Xunit; - -namespace Tests.ConverterTests { - public class UriConverterTests { - [Fact] - public void UriSerializerIsValid() { - UriConverter converter = new UriConverter(); - Uri uri = new Uri("http://localhost:3000/"); - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, uri, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("\"http://localhost:3000/\""); - } - - [Fact] - public void NullUriIsSerializedIntoEmptyString() { - UriConverter converter = new UriConverter(); - Uri? uri = null; - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, uri, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be(""); - } - - [Fact] - public void UriWithQuotesIsSerializedUsingEscapeCharacters() { - UriConverter converter = new UriConverter(); - Uri uri = new Uri("http://localhost:3000/?x=\""); - StringBuilder stringBuilder = new StringBuilder(); - converter.AppendToStringBuilder(stringBuilder, CultureInfo.InvariantCulture, uri, null, ','); - string serialized = stringBuilder.ToString(); - serialized.Should().Be("\"http://localhost:3000/?x=\"\"\""); - } - - [Fact] - public void UriDeserializerIsValid() { - UriConverter converter = new UriConverter(); - string text = "http://localhost:3000/"; - Uri? uri = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - uri.Should().Be(new Uri("http://localhost:3000/")); - } - - [Fact] - public void EmptyStringIsDeserializedIntoNull() { - UriConverter converter = new UriConverter(); - string text = ""; - Uri? uri = converter.Deserialize(text.AsMemory(), CultureInfo.InvariantCulture, null); - uri.Should().BeNull(); - } - - [Fact] - public void EmittedSerializerIsValid() { - UriConverter converter = new UriConverter(); - Uri uri = new Uri("http://localhost:3000/"); - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(Uri), typeof(IFormatProvider), typeof(char) }, typeof(UriConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, local, null, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { uri, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("\"http://localhost:3000/\""); - } - - [Fact] - public void EmittedSerializerSerializesNullUriIntoEmptyString() { - UriConverter converter = new UriConverter(); - Uri? uri = null; - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(Uri), typeof(IFormatProvider), typeof(char) }, typeof(UriConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, local, null, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { uri, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be(""); - } - - [Fact] - public void EmittedSerializerSerializesQuotesIntoEscapeCharacters() { - UriConverter converter = new UriConverter(); - Uri uri = new Uri("http://localhost:3000/?x=\""); - DynamicMethod serialize = new DynamicMethod("Serialize", typeof(string), new Type[] { typeof(Uri), typeof(IFormatProvider), typeof(char) }, typeof(UriConverterTests)); - serialize.GetILGenerator() - .DeclareLocal(out LocalBuilder local) - .Newobj() - .Ldarg_0() - .Emit(gen => converter.EmitAppendToStringBuilder(gen, local, null, null)) - .Callvirt("ToString") - .Ret(); - string serialized = (string)serialize.Invoke(null, new object?[] { uri, CultureInfo.InvariantCulture, ',' })!; - serialized.Should().Be("\"http://localhost:3000/?x=\"\"\""); - } - - [Fact] - public void EmittedDeserializerIsValid() { - UriConverter converter = new UriConverter(); - string text = "http://localhost:3000/"; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(Uri), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(UriConverterTests)); - deserialize.GetILGenerator() - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, null, null, null)) - .Ret(); - Uri? deserialized = (Uri?)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - deserialized.Should().Be(new Uri("http://localhost:3000/")); - } - - [Fact] - public void EmittedDeserializerDeserializesEmptyStringIntoNull() { - UriConverter converter = new UriConverter(); - string text = ""; - DynamicMethod deserialize = new DynamicMethod("Deserialize", typeof(Uri), new Type[] { typeof(ReadOnlyMemory), typeof(IFormatProvider) }, typeof(UriConverterTests)); - deserialize.GetILGenerator() - .Ldarga_S(0) - .Emit(gen => converter.EmitDeserialize(gen, null, null, null)) - .Ret(); - Uri? deserialized = (Uri?)deserialize.Invoke(this, new object?[] { text.AsMemory(), CultureInfo.InvariantCulture })!; - deserialized.Should().BeNull(); - } - } -} diff --git a/Tests/DynamicSerializerTests.cs b/Tests/DynamicSerializerTests.cs index 48a068b..91ed005 100644 --- a/Tests/DynamicSerializerTests.cs +++ b/Tests/DynamicSerializerTests.cs @@ -10,7 +10,7 @@ public class DynamicSerializerTests { public void NullValuesAreSerializedToEmptyColumn() { typeof(ModelWithNullableValues).IsPublic.Should().BeTrue(); - ModelWithNullableValues obj = new ModelWithNullableValues { + ModelWithNullableValues obj = new() { Bool = null, Byte = null, SByte = null, @@ -27,7 +27,10 @@ public void NullValuesAreSerializedToEmptyColumn() { DateTime = null }; string csv = CsvSerializer.Serialize(new[] { obj }, withHeaders: true); - csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\"\r\n,,,,,,,,,,,,,"); + csv.Should().Be(""" + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime" + ,,,,,,,,,,,,, + """); obj = new ModelWithNullableValues { Bool = true, @@ -46,7 +49,10 @@ public void NullValuesAreSerializedToEmptyColumn() { DateTime = new DateTime(2019, 8, 23) }; csv = CsvSerializer.Serialize(new[] { obj }, withHeaders: true); - csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\"\r\nTrue,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,\"CSV Serializer\",\"8/23/2019 12:00:00 AM\""); + csv.Should().Be(""" + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime" + True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","8/23/2019 12:00:00 AM" + """); } [Fact] @@ -72,7 +78,10 @@ public void EmptyColumnsAreDeserializedToNull() { item.String.Should().BeEmpty(); item.DateTime.Should().BeNull(); - csv = "\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\"\r\nTrue,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,\"CSV Serializer\",\"08/23/2019 00:00:00\""; + csv = """ + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime" + True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","08/23/2019 00:00:00" + """; items = CsvSerializer.Deserialize(csv, hasHeaders: true); items.Length.Should().Be(1); item = items.Single(); @@ -95,17 +104,21 @@ public void EmptyColumnsAreDeserializedToNull() { [Fact] public void DoubleQuotesAreEscapedOnSerializing() { typeof(EscapeTest).IsPublic.Should().BeTrue(); - EscapeTest obj = new EscapeTest { + EscapeTest obj = new() { Name = "Tony \"Iron Man\" Stark" }; string csv = CsvSerializer.Serialize(new[] { obj }); - csv.Should().Be("\"Tony \"\"Iron Man\"\" Stark\""); + csv.Should().Be(""" + "Tony ""Iron Man"" Stark" + """); } [Fact] public void DoubleQuotesAreUnescapedOnDeserializing() { typeof(EscapeTest).IsPublic.Should().BeTrue(); - string csv = "\"Tony \"\"Iron Man\"\" Stark\""; + string csv = """ + "Tony ""Iron Man"" Stark" + """; EscapeTest[] items = CsvSerializer.Deserialize(csv); items.Length.Should().Be(1); EscapeTest item = items[0]; @@ -115,7 +128,10 @@ public void DoubleQuotesAreUnescapedOnDeserializing() { [Fact] public void CommasInStringDontSplitString() { typeof(CommaTest).IsPublic.Should().BeTrue(); - string csv = "\"Stark, Tony\",\"Stark\"\r\n\"Banner, Bruce\",\"Banner\""; + string csv = """ + "Stark, Tony","Stark" + "Banner, Bruce","Banner" + """; CommaTest[] items = CsvSerializer.Deserialize(csv); items.Length.Should().Be(2); items[0].Name.Should().Be("Stark, Tony"); diff --git a/Tests/InternationalizationTests.cs b/Tests/InternationalizationTests.cs index 5c86a1b..67b3607 100644 --- a/Tests/InternationalizationTests.cs +++ b/Tests/InternationalizationTests.cs @@ -1,21 +1,20 @@ using Csv; using FluentAssertions; using System; -using System.Collections.Generic; using System.Globalization; using Xunit; namespace Tests { public class InternationalizationTests { - private class MapCoordinates { - public double Latitude { get; set; } - public double Longitude { get; set; } + private sealed record MapCoordinates { + public double Latitude { get; init; } + public double Longitude { get; init; } } [Fact] public void CanSerializeAndDeserializeInEN_USLocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("en-US"); - DecimalAndDouble model = new DecimalAndDouble { + DecimalAndDouble model = new() { Decimal = 123_456.789m, Double = 123_456.789 }; @@ -30,7 +29,7 @@ public void CanSerializeAndDeserializeInEN_USLocale() { [Fact] public void CanSerializeAndDeserializeInEN_GBLocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("en-GB"); - DecimalAndDouble model = new DecimalAndDouble { + DecimalAndDouble model = new() { Decimal = 123_456.789m, Double = 123_456.789 }; @@ -47,7 +46,7 @@ public void CanSerializeAndDeserializeWithModifiedThreadCulture() { CultureInfo temp = CultureInfo.CurrentCulture; try { CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("id-ID"); - DecimalAndDouble model = new DecimalAndDouble { + DecimalAndDouble model = new() { Decimal = 123_456.789m, Double = 123_456.789 }; @@ -65,7 +64,7 @@ public void CanSerializeAndDeserializeWithModifiedThreadCulture() { [Fact] public void CanSerializeAndDeserializeInID_IDLocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("id-ID"); - DecimalAndDouble model = new DecimalAndDouble { + DecimalAndDouble model = new() { Decimal = 123_456.789m, Double = 123_456.789 }; @@ -80,7 +79,7 @@ public void CanSerializeAndDeserializeInID_IDLocale() { [Fact] public void CanSerializeAndDeserializeInEN_INLocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("en-IN"); - DecimalAndDouble model = new DecimalAndDouble { + DecimalAndDouble model = new() { Decimal = 123_456.789m, Double = 123_456.789 }; @@ -95,7 +94,7 @@ public void CanSerializeAndDeserializeInEN_INLocale() { [Fact] public void CanSerializeAndDeserializeInFR_FRLocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("fr-FR"); - DecimalAndDouble model = new DecimalAndDouble { + DecimalAndDouble model = new() { Decimal = 123_456.789m, Double = 123_456.789 }; @@ -110,7 +109,7 @@ public void CanSerializeAndDeserializeInFR_FRLocale() { [Fact] public void CanSerializeAndDeserializeInDE_DELocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("de-DE"); - DecimalAndDouble model = new DecimalAndDouble { + DecimalAndDouble model = new() { Decimal = 123_456.789m, Double = 123_456.789 }; @@ -125,7 +124,7 @@ public void CanSerializeAndDeserializeInDE_DELocale() { [Fact] public void CanSerializeAndDeserializeInZH_CNLocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("zh-CN"); - DecimalAndDouble model = new DecimalAndDouble { + DecimalAndDouble model = new() { Decimal = 123_456.789m, Double = 123_456.789 }; @@ -140,7 +139,7 @@ public void CanSerializeAndDeserializeInZH_CNLocale() { [Fact] public void CanSerializeAndDeserializeInAR_SALocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("ar-SA"); - DecimalAndDouble model = new DecimalAndDouble { + DecimalAndDouble model = new() { Decimal = 123_456.789m, Double = 123_456.789 }; diff --git a/Tests/NaiveSerializerTests.cs b/Tests/NaiveSerializerTests.cs index 3c49521..843c6c3 100644 --- a/Tests/NaiveSerializerTests.cs +++ b/Tests/NaiveSerializerTests.cs @@ -10,7 +10,7 @@ public class NaiveSerializerTests { public void NullValuesAreSerializedIntoEmptyColumn() { typeof(ModelWithNullableValues).IsPublic.Should().BeFalse(); - ModelWithNullableValues obj = new ModelWithNullableValues { + ModelWithNullableValues obj = new() { Bool = null, Byte = null, SByte = null, @@ -27,7 +27,10 @@ public void NullValuesAreSerializedIntoEmptyColumn() { DateTime = null }; string csv = CsvSerializer.Serialize(new[] { obj }, withHeaders: true); - csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\"\r\n,,,,,,,,,,,,,"); + csv.Should().Be(""" + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime" + ,,,,,,,,,,,,, + """); obj = new ModelWithNullableValues { Bool = true, @@ -46,7 +49,10 @@ public void NullValuesAreSerializedIntoEmptyColumn() { DateTime = new DateTime(2019, 8, 23) }; csv = CsvSerializer.Serialize(new[] { obj }, withHeaders: true); - csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\"\r\nTrue,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,\"CSV Serializer\",\"8/23/2019 12:00:00 AM\""); + csv.Should().Be(""" + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime" + True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","8/23/2019 12:00:00 AM" + """); } [Fact] @@ -72,7 +78,10 @@ public void EmptyColumnsAreDeserializedIntoNull() { item.String.Should().BeEmpty(); item.DateTime.Should().BeNull(); - csv = "\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\"\r\nTrue,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,\"CSV Serializer\",\"08/23/2019 00:00:00\""; + csv = """ + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime" + True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","08/23/2019 00:00:00" + """; items = CsvSerializer.Deserialize(csv, hasHeaders: true); items.Length.Should().Be(1); item = items.Single(); @@ -98,30 +107,37 @@ public void DoubleQuotesAreEscapedOnSerializing() { Name = "Tony \"Iron Man\" Stark" }; string csv = CsvSerializer.Serialize(new[] { obj }); - csv.Should().Be("\"Tony \"\"Iron Man\"\" Stark\""); + csv.Should().Be(""" + "Tony ""Iron Man"" Stark" + """); } - class EscapeTest { + public sealed record EscapeTest { public string? Name { get; set; } } [Fact] public void DoubleQuotesAreUnescapedOnDeserializing() { typeof(EscapeTest).IsPublic.Should().BeFalse(); - string csv = "\"Tony \"\"Iron Man\"\" Stark\""; + string csv = """ + "Tony ""Iron Man"" Stark" + """; EscapeTest[] items = CsvSerializer.Deserialize(csv); items.Length.Should().Be(1); EscapeTest item = items[0]; item.Name.Should().Be("Tony \"Iron Man\" Stark"); } - class CommaTest { + public sealed record CommaTest { public string? Name { get; set; } public string? LastName { get; set; } } [Fact] public void CommasInStringDontSplitString() { typeof(CommaTest).IsPublic.Should().BeFalse(); - string csv = "\"Stark, Tony\",\"Stark\"\r\n\"Banner, Bruce\",\"Banner\""; + string csv = """ + "Stark, Tony","Stark" + "Banner, Bruce","Banner" + """; CommaTest[] items = CsvSerializer.Deserialize(csv); items.Length.Should().Be(2); items[0].Name.Should().Be("Stark, Tony"); @@ -130,7 +146,7 @@ public void CommasInStringDontSplitString() { items[1].LastName.Should().Be("Banner"); } - class ModelWithNullableValues { + public sealed record ModelWithNullableValues { public bool? Bool { get; set; } public byte? Byte { get; set; } public sbyte? SByte { get; set; } diff --git a/Tests/SerializerTests.cs b/Tests/SerializerTests.cs index 0296fb3..1961628 100644 --- a/Tests/SerializerTests.cs +++ b/Tests/SerializerTests.cs @@ -28,13 +28,17 @@ public void AnonymousTypesAreSerializedUsingNaiveSerializer() { Uri = new Uri("http://localhost:5000/"), StatusCode = HttpStatusCode.OK }; + string csv = CsvSerializer.Serialize(new[] { item }, withHeaders: true); - csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\",\"Uri\",\"StatusCode\"\r\nTrue,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,\"CSV Serializer\",\"8/23/2019 12:00:00 AM\",\"http://localhost:5000/\",OK"); + csv.Should().Be(""" + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime","Uri","StatusCode" + True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","8/23/2019 12:00:00 AM","http://localhost:5000/",OK + """); } [Fact] public void PublicTypesAreSerializedUsingDynamicSerializer() { - Model item = new Model { + Model item = new() { Bool = true, Byte = 0x66, SByte = -100, @@ -53,16 +57,22 @@ public void PublicTypesAreSerializedUsingDynamicSerializer() { StatusCode = HttpStatusCode.OK }; string csv = CsvSerializer.Serialize(new[] { item }, withHeaders: true); - csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\",\"Uri\",\"StatusCode\"\r\nTrue,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,\"CSV Serializer\",\"8/23/2019 12:00:00 AM\",\"http://localhost:5000/\",OK"); + csv.Should().Be(""" + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime","Uri","StatusCode" + True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","8/23/2019 12:00:00 AM","http://localhost:5000/",OK + """); } [Fact] public void PrivateTypesAreSerializedUsingNaiveSerializer() { - PrivateModel item = new PrivateModel { + PrivateModel item = new() { Name = "CSV Serializer" }; string csv = CsvSerializer.Serialize(new[] { item }, withHeaders: true); - csv.Should().Be("\"Name\"\r\n\"CSV Serializer\""); + csv.Should().Be(""" + "Name" + "CSV Serializer" + """); } [Fact] @@ -89,7 +99,7 @@ public void Serializing1MillionRowsOfAnonymousTypeCompletesIn10Seconds() { [Fact] public void Serializing1MillionRowsOfPublicTypeCompletesIn10Seconds() { - Model item = new Model { + Model item = new() { Bool = true, Byte = 0x66, SByte = -100, @@ -111,7 +121,7 @@ public void Serializing1MillionRowsOfPublicTypeCompletesIn10Seconds() { [Fact] public void Deserializing1MillionRowsOfPublicTypeCompletesIn20Seconds() { - Model item = new Model { + Model item = new() { Bool = true, Byte = 0x66, SByte = -100, @@ -135,7 +145,10 @@ public void Deserializing1MillionRowsOfPublicTypeCompletesIn20Seconds() { [Fact] public void CsvCanBeDeserializedToPublicType() { - string csv = "\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\",\"Uri\",\"StatusCode\"\r\nTrue,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,\"CSV Serializer\",\"08/23/2019 00:00:00\",\"http://localhost:5000/\",OK"; + string csv = """ + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime","Uri","StatusCode" + True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","08/23/2019 00:00:00","http://localhost:5000/",OK + """; Model[] items = CsvSerializer.Deserialize(csv, hasHeaders: true); items.Length.Should().Be(1); Model item = items.Single(); @@ -180,9 +193,11 @@ public void HeaderNameCanBeRenamedUsingAttribute() { [Fact] public void CanDeserializeCsvFromExcel() { - string csv = @"No;Name;Price;Weight;CreatedDate;IsSuspended -10;Deflector, Dust (For Rear Differential);200000;20,5;13/12/2019;FALSE -13;""Deflector; Tire; Filter"";150000;15,5;20/11/2019;TRUE"; + string csv = """ + No;Name;Price;Weight;CreatedDate;IsSuspended + 10;Deflector, Dust (For Rear Differential);200000;20,5;13/12/2019;FALSE + 13;"Deflector; Tire; Filter";150000;15,5;20/11/2019;TRUE + """; ExcelModel[] models = CsvSerializer.Deserialize(csv, hasHeaders: true, delimiter: ';', provider: CultureInfo.GetCultureInfo("id-ID")); models.Count().Should().Be(2); } @@ -190,7 +205,7 @@ public void CanDeserializeCsvFromExcel() { public class ExcelModel { public int No { get; set; } - public string Name { get; set; } + public required string Name { get; set; } public decimal Price { get; set; } public double Weight { get; set; } [CsvColumn("CreatedDate", DateFormat = "dd/MM/yyyy")] @@ -217,7 +232,7 @@ public class Model { public HttpStatusCode? StatusCode { get; set; } } - class PrivateModel { + internal sealed class PrivateModel { public string? Name { get; set; } } diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 9b0b772..4411b17 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,24 +1,27 @@  - netcoreapp3.0 + net7.0 enable false - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - +