Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .NET Standard 2.0 target #97

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
end_of_line = unset
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4

[*.{proj,props,sln,targets,sql}]
indent_style = tab

[*.{dna,config,nuspec,xml,xsd,csproj,vcxproj,vcproj,targets,ps1,resx}]
indent_size = 2

[*.{cpp,h,def}]
indent_style = tab

[*.dotsettings]
end_of_line = lf

[*.sas]
indent_style = tab
end_of_line = lf
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ coverage.xml
.vscode/
.idea/
*.user
/_ReSharper.Caches/ReSharperPlatformVs17233_c49249f4.RecordParser.00
2 changes: 1 addition & 1 deletion RecordParser.Benchmark/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private static T ProcessSequence<T>(ReadOnlySequence<byte> sequence, FuncSpanT<T

if (sequence.IsSingleSegment)
{
return Parse(sequence.FirstSpan, parser);
return Parse(sequence.First.Span, parser);
}

var length = (int)sequence.Length;
Expand Down
10 changes: 5 additions & 5 deletions RecordParser.Benchmark/CursivelyPersonVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public override void VisitEndOfField(ReadOnlySpan<byte> chunk)
if (_dataBufUsed != 0)
{
VisitPartialFieldContents(chunk);
chunk = _dataBuf.AsSpan(.._dataBufUsed);
chunk = _dataBuf.AsSpan(0, _dataBufUsed);
_dataBufUsed = 0;
}

Expand All @@ -55,8 +55,8 @@ public override void VisitEndOfField(ReadOnlySpan<byte> chunk)
case 3:
// M/d/yyyy format is not supported by this for some reason.
////_ = Utf8Parser.TryParse(chunk, out _person.birthday, out _);
Span<char> birthdayChars = _decodeBuf.AsSpan(..Encoding.UTF8.GetChars(chunk, _decodeBuf));
_person.birthday = DateTime.Parse(birthdayChars, DateTimeFormatInfo.InvariantInfo);
Span<char> birthdayChars = _decodeBuf.AsSpan(0, Encoding.UTF8.GetChars(chunk, _decodeBuf));
_person.birthday = Parse.DateTime(birthdayChars, DateTimeFormatInfo.InvariantInfo);
break;

case 4:
Expand All @@ -67,7 +67,7 @@ public override void VisitEndOfField(ReadOnlySpan<byte> chunk)
// N.B.: there are ways to improve the efficiency of this for earlier
// targets, but I think it's fine for performance-sensitive applications to
// have to upgrade to .NET 6.0 or higher...
_person.gender = Enum.Parse<Gender>(Encoding.UTF8.GetString(chunk));
_person.gender = Parse.Enum<Gender>(Encoding.UTF8.GetString(chunk).AsSpan());
#endif
break;

Expand All @@ -91,7 +91,7 @@ public override void VisitEndOfRecord()
public override void VisitPartialFieldContents(ReadOnlySpan<byte> chunk)
{
EnsureCapacity(_dataBufUsed + chunk.Length);
chunk.CopyTo(_dataBuf.AsSpan(_dataBufUsed..));
chunk.CopyTo(_dataBuf.AsSpan(_dataBufUsed));
_dataBufUsed += chunk.Length;
}

Expand Down
14 changes: 7 additions & 7 deletions RecordParser.Benchmark/FixedLengthReaderBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public async Task Read_FixedLength_ManualString()
name = line.Substring(2, 30).Trim(),
age = int.Parse(line.Substring(32, 2)),
birthday = DateTime.Parse(line.Substring(39, 10), CultureInfo.InvariantCulture),
gender = Enum.Parse<Gender>(line.Substring(85, 6)),
gender = Parse.Enum<Gender>(line.Substring(85, 6).AsSpan()),
email = line.Substring(92, 22).Trim(),
children = bool.Parse(line.Substring(121, 5))
};
Expand Down Expand Up @@ -151,12 +151,12 @@ await ProcessFlatFile((ReadOnlySpan<char> line) =>
return new Person
{
alfa = line[0],
name = new string(line.Slice(2, 30).Trim()),
age = int.Parse(line.Slice(32, 2)),
birthday = DateTime.Parse(line.Slice(39, 10), CultureInfo.InvariantCulture),
gender = Enum.Parse<Gender>(line.Slice(85, 6)),
email = new string(line.Slice(92, 22).Trim()),
children = bool.Parse(line.Slice(121, 5))
name = line.Slice(2, 30).Trim().ToString(),
age = Parse.Int32(line.Slice(32, 2)),
birthday = Parse.DateTime(line.Slice(39, 10), CultureInfo.InvariantCulture),
gender = Parse.Enum<Gender>(line.Slice(85, 6)),
email = line.Slice(92, 22).Trim().ToString(),
children = Parse.Boolean(line.Slice(121, 5))
};
});

Expand Down
53 changes: 53 additions & 0 deletions RecordParser.Benchmark/Parse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Globalization;
using System.Runtime.CompilerServices;

namespace RecordParser.Benchmark
{
internal static class Parse
{
#if NETSTANDARD2_0 || NETFRAMEWORK
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string ProcessSpan(ReadOnlySpan<char> span) => span.ToString();
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ReadOnlySpan<char> ProcessSpan(ReadOnlySpan<char> span) => span;
#endif

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Byte(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => byte.Parse(ProcessSpan(utf8Text), NumberStyles.Integer, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static sbyte SByte(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => sbyte.Parse(ProcessSpan(utf8Text), NumberStyles.Integer, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Double(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => double.Parse(ProcessSpan(utf8Text), NumberStyles.AllowThousands | NumberStyles.Float, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Single(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => float.Parse(ProcessSpan(utf8Text), NumberStyles.AllowThousands | NumberStyles.Float, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Int32(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => int.Parse(ProcessSpan(utf8Text), NumberStyles.Integer, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Guid Guid(ReadOnlySpan<char> utf8Text) => System.Guid.Parse(ProcessSpan(utf8Text));

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTime DateTime(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => System.DateTime.Parse(ProcessSpan(utf8Text), provider, DateTimeStyles.AllowWhiteSpaces);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Boolean(ReadOnlySpan<char> utf8Text) => bool.Parse(ProcessSpan(utf8Text));

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TEnum Enum<TEnum>(ReadOnlySpan<char> utf8Text) where TEnum : struct, Enum
{
#if NETSTANDARD2_0 || NETFRAMEWORK
return (TEnum)System.Enum.Parse(typeof(TEnum), utf8Text.ToString());
#elif NETSTANDARD2_1
return System.Enum.Parse<TEnum>(utf8Text.ToString());
#else
return System.Enum.Parse<TEnum>(utf8Text);
#endif
}
}
}
13 changes: 12 additions & 1 deletion RecordParser.Benchmark/RecordParser.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0</TargetFrameworks>
<TargetFrameworks>net472;net6.0;net7.0;net8.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<!--<DefineConstants>TEST_ALL</DefineConstants>-->
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)'=='net472'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Ben.StringIntern" Version="0.1.8" />
<PackageReference Include="Cursively" Version="1.2.0" />
Expand All @@ -19,6 +23,13 @@
<PackageReference Include="ZString" Version="2.5.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net472'">
<PackageReference Include="PolySharp" Version="1.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\RecordParser\RecordParser.csproj" />
</ItemGroup>
Expand Down
72 changes: 72 additions & 0 deletions RecordParser.Benchmark/Shims.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#if NETSTANDARD2_0 || NETFRAMEWORK

using System;
using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RecordParser.Benchmark
{
internal static class Shims
{
public static int GetChars(this Encoding encoding, ReadOnlySpan<byte> bytes, Span<char> chars)
{
unsafe
{
fixed (byte* b = &MemoryMarshal.GetReference(bytes))
{
int charCount = encoding.GetCharCount(b, bytes.Length);
if (charCount > chars.Length) return 0;

fixed (char* c = &MemoryMarshal.GetReference(chars))
{
return encoding.GetChars(b, bytes.Length, c, chars.Length);
}
}
}
}

public static string GetString(this Encoding encoding, scoped ReadOnlySpan<byte> bytes)
{
if (bytes.IsEmpty) return string.Empty;

unsafe
{
fixed (byte* pB = &MemoryMarshal.GetReference(bytes))
{
return encoding.GetString(pB, bytes.Length);
}
}
}

public static Task WriteLineAsync(this TextWriter writer, ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
{
if (MemoryMarshal.TryGetArray(value, out var arraySegment))
{
return arraySegment.Array is null ? Task.CompletedTask : writer.WriteLineAsync(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
}

return Impl(writer, value);

static async Task Impl(TextWriter writer, ReadOnlyMemory<char> value)
{
var pool = ArrayPool<char>.Shared;
var array = pool.Rent(value.Length);
try
{
value.CopyTo(array.AsMemory());
await writer.WriteLineAsync(array, 0, value.Length);
}
finally
{
pool.Return(array);
}
}
}
}
}

#endif
16 changes: 8 additions & 8 deletions RecordParser.Benchmark/VariableLengthReaderBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ public async Task Read_VariableLength_ManualString()
{
if (i++ == LimitRecord) return;

var coluns = line.Split(",");
var coluns = line.Split(',');
var person = new Person()
{
id = Guid.Parse(coluns[0]),
name = coluns[1].Trim(),
age = int.Parse(coluns[2]),
birthday = DateTime.Parse(coluns[3], CultureInfo.InvariantCulture),
gender = Enum.Parse<Gender>(coluns[4]),
gender = Parse.Enum<Gender>(coluns[4].AsSpan()),
email = coluns[5].Trim(),
children = bool.Parse(coluns[7])
};
Expand Down Expand Up @@ -203,7 +203,7 @@ Person PersonFactory(Func<int, string> getColumnValue)
name = getColumnValue(1).Trim(),
age = int.Parse(getColumnValue(2)),
birthday = DateTime.Parse(getColumnValue(3), CultureInfo.InvariantCulture),
gender = Enum.Parse<Gender>(getColumnValue(4)),
gender = Parse.Enum<Gender>(getColumnValue(4).AsSpan()),
email = getColumnValue(5).Trim(),
children = bool.Parse(getColumnValue(7))
};
Expand Down Expand Up @@ -262,13 +262,13 @@ await ProcessCSVFile((ReadOnlySpan<char> line) =>

return new Person
{
id = Guid.Parse(id),
id = Parse.Guid(id),
name = name.ToString(),
age = int.Parse(age),
birthday = DateTime.Parse(birthday, DateTimeFormatInfo.InvariantInfo),
gender = Enum.Parse<Gender>(gender),
age = Parse.Int32(age),
birthday = Parse.DateTime(birthday, DateTimeFormatInfo.InvariantInfo),
gender = Parse.Enum<Gender>(gender),
email = email.ToString(),
children = bool.Parse(children)
children = Parse.Boolean(children)
};
});
}
Expand Down
2 changes: 1 addition & 1 deletion RecordParser.Benchmark/VariableLengthWriterBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public async Task Write_VariableLength_ManualString()
sb.Append(";");
sb.Append(person.children);

await streamWriter.WriteLineAsync(sb);
await streamWriter.WriteLineAsync(sb.ToString());
sb.Clear();
}
}
Expand Down
6 changes: 3 additions & 3 deletions RecordParser.Test/FileReaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
[InlineData(13, 5)]
[InlineData(14, 7)]
[InlineData(15, 7)]
public void Given_record_is_too_large_for_custom_buffer_size_then_exception_should_be_throw(int bufferSize, int canRead)

Check warning on line 115 in RecordParser.Test/FileReaderTest.cs

View workflow job for this annotation

GitHub Actions / dotnet test

Theory method 'Given_record_is_too_large_for_custom_buffer_size_then_exception_should_be_throw' on test class 'FileReaderTest' does not use parameter 'bufferSize'. Use the parameter, or remove the parameter and associated data. (https://xunit.net/xunit.analyzers/rules/xUnit1026)
{
// Arrange

Expand Down Expand Up @@ -223,7 +223,7 @@

[Theory]
[MemberData(nameof(Given_quoted_csv_file_should_read_quoted_properly_theory), new object[] { "AllFieldsQuotedCsv.csv" })]
public void Read_csv_file_all_fields_quoted(string fileContent, bool hasHeader, bool parallelProcessing, bool blankLineAtEnd, int repeat)

Check warning on line 226 in RecordParser.Test/FileReaderTest.cs

View workflow job for this annotation

GitHub Actions / dotnet test

Theory method 'Read_csv_file_all_fields_quoted' on test class 'FileReaderTest' does not use parameter 'blankLineAtEnd'. Use the parameter, or remove the parameter and associated data. (https://xunit.net/xunit.analyzers/rules/xUnit1026)
{
// Arrange

Expand All @@ -246,7 +246,7 @@
new () { id = new Guid("63858071-cbb3-5abd-9f88-3dfd565cc4ab"), name = "Lucy Berry", age = 49, birthday = DateTime.Parse("11/12/1961"), gender = Gender.Female, email = "[email protected]", children = false },
new () { id = new Guid("203804f9-93e7-5510-8bb2-177296bafe6a"), name = "Frank Fox", age = 36, birthday = DateTime.Parse("3/19/1977"), gender = Gender.Male, email = "[email protected]", children = true },
new () { id = new Guid("a8af66fb-bad4-51eb-810c-bf3ca22337c6"), name = "Isabel Todd", age = 51, birthday = DateTime.Parse("9/16/1999"), gender = Gender.Female, email = "[email protected]", children = false },
new () { id = new Guid("1a3d8a66-3e0c-50eb-99c1-a3926bce15ed"), name = $"Joseph {Environment.NewLine}Scott", age = 55, birthday = DateTime.Parse("10/26/1986"), gender = Gender.Male, email = "[email protected]", children = false },
new () { id = new Guid("1a3d8a66-3e0c-50eb-99c1-a3926bce15ed"), name = "Joseph \r\nScott", age = 55, birthday = DateTime.Parse("10/26/1986"), gender = Gender.Male, email = "[email protected]", children = false },
new () { id = new Guid("aa7d4395-f10f-5776-9912-e3d86c4b9d3c"), name = "Gilbert Brooks", age = 56, birthday = DateTime.Parse("3/1/1956"), gender = Gender.Female, email = "[email protected]", children = true },
new () { id = new Guid("1d25b811-4002-5744-ac40-93a50f2a442c"), name = "Louis \"Ronaldo\" Bennett", age = 25, birthday = DateTime.Parse("4/4/1967"), gender = Gender.Male, email = "[email protected]", children = true },
new () { id = new Guid("8e963ae5-a9ed-5572-b11c-566abc6a8a56"), name = "Norman Parker", age = 57, birthday = DateTime.Parse("4/17/1969"), gender = Gender.Male, email = "[email protected]", children = true },
Expand All @@ -273,7 +273,7 @@

[Theory]
[MemberData(nameof(Given_quoted_csv_file_should_read_quoted_properly_theory), new object[] { "QuotedCsv.csv" })]
public void Read_quoted_csv_file(string fileContent, bool hasHeader, bool parallelProcessing, bool blankLineAtEnd, int repeat)

Check warning on line 276 in RecordParser.Test/FileReaderTest.cs

View workflow job for this annotation

GitHub Actions / dotnet test

Theory method 'Read_quoted_csv_file' on test class 'FileReaderTest' does not use parameter 'blankLineAtEnd'. Use the parameter, or remove the parameter and associated data. (https://xunit.net/xunit.analyzers/rules/xUnit1026)
{
// Arrange

Expand All @@ -291,7 +291,7 @@
var expectedItems = new Quoted[]
{
new Quoted { Id = 1, Date = new DateTime(2010, 01, 02), Name = "Ana", Rate = "Good", Ranking = 56 },
new Quoted { Id = 2, Date = new DateTime(2011, 05, 12), Name = "Bob", Rate = $"Much {Environment.NewLine}Good", Ranking = 4 },
new Quoted { Id = 2, Date = new DateTime(2011, 05, 12), Name = "Bob", Rate = "Much \r\nGood", Ranking = 4 },
new Quoted { Id = 3, Date = new DateTime(2013, 12, 10), Name = "Carla", Rate = "\"Medium\"", Ranking = 5 },
new Quoted { Id = 4, Date = new DateTime(2015, 03, 03), Name = "Derik", Rate = "Absolute, Awesome", Ranking = 1 },
}
Expand Down Expand Up @@ -363,7 +363,7 @@

[Theory]
[MemberData(nameof(Given_not_quoted_csv_file_should_read_quoted_properly_theory))]
public void Read_not_quoted_csv_file(string fileContent, bool hasHeader, bool parallelProcessing, bool blankLineAtEnd, bool containgQuote, int repeat)

Check warning on line 366 in RecordParser.Test/FileReaderTest.cs

View workflow job for this annotation

GitHub Actions / dotnet test

Theory method 'Read_not_quoted_csv_file' on test class 'FileReaderTest' does not use parameter 'blankLineAtEnd'. Use the parameter, or remove the parameter and associated data. (https://xunit.net/xunit.analyzers/rules/xUnit1026)
{
// Arrange

Expand Down Expand Up @@ -537,7 +537,7 @@

[Theory]
[MemberData(nameof(Given_fixed_length_file_should_read_quoted_properly_theory))]
public void Read_plain_text_of_fixed_length_file(string fileContent, bool parallelProcessing, bool blankLineAtEnd, int repeat)

Check warning on line 540 in RecordParser.Test/FileReaderTest.cs

View workflow job for this annotation

GitHub Actions / dotnet test

Theory method 'Read_plain_text_of_fixed_length_file' on test class 'FileReaderTest' does not use parameter 'parallelProcessing'. Use the parameter, or remove the parameter and associated data. (https://xunit.net/xunit.analyzers/rules/xUnit1026)

Check warning on line 540 in RecordParser.Test/FileReaderTest.cs

View workflow job for this annotation

GitHub Actions / dotnet test

Theory method 'Read_plain_text_of_fixed_length_file' on test class 'FileReaderTest' does not use parameter 'blankLineAtEnd'. Use the parameter, or remove the parameter and associated data. (https://xunit.net/xunit.analyzers/rules/xUnit1026)

Check warning on line 540 in RecordParser.Test/FileReaderTest.cs

View workflow job for this annotation

GitHub Actions / dotnet test

Theory method 'Read_plain_text_of_fixed_length_file' on test class 'FileReaderTest' does not use parameter 'repeat'. Use the parameter, or remove the parameter and associated data. (https://xunit.net/xunit.analyzers/rules/xUnit1026)
{
// Arrange

Expand Down Expand Up @@ -569,4 +569,4 @@
result.Should().BeEquivalentTo(expected, cfg => cfg.WithStrictOrdering());
}
}
}
}
4 changes: 2 additions & 2 deletions RecordParser.Test/FileWriterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void Write_csv_file(int repeat, bool parallel, bool ordered)

var reader = new VariableLengthReaderBuilder<(string Name, DateTime Birthday, decimal Money, Color Color, int Index)>()
.Map(x => x.Name, 0)
.Map(x => x.Birthday, 1, value => new DateTime(long.Parse(value)))
.Map(x => x.Birthday, 1, value => new DateTime(Parse.Int64(value)))
.Map(x => x.Money, 2)
.Map(x => x.Color, 3)
.Map(x => x.Index, 4)
Expand Down Expand Up @@ -97,4 +97,4 @@ public void Write_csv_file(int repeat, bool parallel, bool ordered)
items.Should().BeEquivalentTo(expectedItems);
}
}
}
}
Loading
Loading