diff --git a/src/ZLogger/Formatters/PlainTextZLoggerFormatter.cs b/src/ZLogger/Formatters/PlainTextZLoggerFormatter.cs index e9f7583a..b1bd1569 100644 --- a/src/ZLogger/Formatters/PlainTextZLoggerFormatter.cs +++ b/src/ZLogger/Formatters/PlainTextZLoggerFormatter.cs @@ -98,7 +98,7 @@ static void Write(IBufferWriter writer, ReadOnlySpan message1, strin { var span = writer.GetSpan(message1.Length + Encoding.UTF8.GetMaxByteCount(message2.Length)); message1.CopyTo(span); - var written2 = Encoding.UTF8.GetBytes(message2, span.Slice(message1.Length)); + var written2 = Encoding.UTF8.GetBytes(message2.AsSpan(), span.Slice(message1.Length)); writer.Advance(message1.Length + written2); } @@ -106,9 +106,9 @@ static void Write(IBufferWriter writer, string message1, string message2, { var span = writer.GetSpan(Encoding.UTF8.GetMaxByteCount(message1.Length + message2.Length + message3.Length)); - var written1 = Encoding.UTF8.GetBytes(message1, span); - var written2 = Encoding.UTF8.GetBytes(message2, span.Slice(written1)); - var written3 = Encoding.UTF8.GetBytes(message3, span.Slice(written1 + written2)); + var written1 = Encoding.UTF8.GetBytes(message1.AsSpan(), span); + var written2 = Encoding.UTF8.GetBytes(message2.AsSpan(), span.Slice(written1)); + var written3 = Encoding.UTF8.GetBytes(message3.AsSpan(), span.Slice(written1 + written2)); writer.Advance(written1 + written2 + written3); } } diff --git a/src/ZLogger/Formatters/SystemTextJsonZLoggerFormatter.cs b/src/ZLogger/Formatters/SystemTextJsonZLoggerFormatter.cs index 2a5a6b0e..0674e1df 100644 --- a/src/ZLogger/Formatters/SystemTextJsonZLoggerFormatter.cs +++ b/src/ZLogger/Formatters/SystemTextJsonZLoggerFormatter.cs @@ -88,7 +88,7 @@ public void FormatLogEntry(IBufferWriter writer, TEntry entry) whe // If `BeginScope(format, arg1, arg2)` style is used, the first argument `format` string is passed with this name if (x.Key == "{OriginalFormat}") continue; - WriteMutatedJsonKeyName(x.Key, jsonWriter, KeyNameMutator); + WriteMutatedJsonKeyName(x.Key.AsSpan(), jsonWriter, KeyNameMutator); if (x.Value is { } value) { diff --git a/src/ZLogger/Internal/EnumDictionary.cs b/src/ZLogger/Internal/EnumDictionary.cs index f2147f7a..be42b4e7 100644 --- a/src/ZLogger/Internal/EnumDictionary.cs +++ b/src/ZLogger/Internal/EnumDictionary.cs @@ -41,7 +41,14 @@ public static EnumDictionary Create() { // dictionary key is enumValue, value is name. var unboxedEnumValue = (T)enumValue; - var key = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref unboxedEnumValue), Unsafe.SizeOf()).ToArray(); + + var keySpan = +#if NETSTANDARD2_0 + Shims.CreateReadOnlySpan(ref Unsafe.As(ref unboxedEnumValue), Unsafe.SizeOf()); +#else + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref unboxedEnumValue), Unsafe.SizeOf()); +#endif + var key = keySpan.ToArray(); var value1 = Encoding.UTF8.GetBytes(name); var value2 = JsonEncodedText.Encode(value1); @@ -154,20 +161,12 @@ static int GetBytesHashCode(ReadOnlySpan bytes) } } - readonly struct Entry + readonly struct Entry(byte[] key, string name, byte[] utf8Name, JsonEncodedText jsonEncoded) { - public readonly byte[] Key; - public readonly string Name; - public readonly byte[] Utf8Name; - public readonly JsonEncodedText JsonEncoded; - - public Entry(byte[] key, string name, byte[] utf8Name, JsonEncodedText jsonEncoded) - { - Key = key; - Name = name; - Utf8Name = utf8Name; - JsonEncoded = jsonEncoded; - } + public readonly byte[] Key = key; + public readonly string Name = name; + public readonly byte[] Utf8Name = utf8Name; + public readonly JsonEncodedText JsonEncoded = jsonEncoded; // for debugging public override string ToString() diff --git a/src/ZLogger/Internal/MagicalBox.cs b/src/ZLogger/Internal/MagicalBox.cs index 25e17e82..ef5661e8 100644 --- a/src/ZLogger/Internal/MagicalBox.cs +++ b/src/ZLogger/Internal/MagicalBox.cs @@ -1,4 +1,5 @@ -using System.Buffers; +using System; +using System.Buffers; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -23,7 +24,7 @@ public MagicalBox(byte[] storage) public bool TryWrite(T value, out int offset) { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) + if (!IsSupportedType()) { offset = 0; return false; @@ -47,7 +48,7 @@ public bool TryWrite(T value, out int offset) public bool TryRead(int offset, out T value) { - if (RuntimeHelpers.IsReferenceOrContainsReferences() + if (!IsSupportedType() || offset < 0 || (storage.Length < offset + Unsafe.SizeOf())) { @@ -77,7 +78,11 @@ public T Read(int offset) public ReadOnlySpan ReadRawEnumValue(Type type, int offset) { var (_, size, _) = ReaderCache.GetEnumDictionaryAndValueSize(type); +#if NETSTANDARD2_0 + return Shims.CreateReadOnlySpan(ref storage[offset], size); +#else return MemoryMarshal.CreateReadOnlySpan(ref storage[offset], size); +#endif } public bool TryReadTo(Type type, int offset, int alignment, string? format, ref Utf8StringWriter> handler) @@ -136,7 +141,12 @@ public bool TryReadTo(Type type, int offset, int alignment, string? format, ref if (type.IsEnum) { var (dict, size, converter) = ReaderCache.GetEnumDictionaryAndValueSize(type); - var rawValue = MemoryMarshal.CreateReadOnlySpan(ref storage[offset], size); + var rawValue = +#if NETSTANDARD2_0 + Shims.CreateReadOnlySpan(ref storage[offset], size); +#else + MemoryMarshal.CreateReadOnlySpan(ref storage[offset], size); +#endif var name = dict.GetUtf8Name(rawValue); if (name == null) { @@ -234,7 +244,11 @@ public bool TryReadTo(Type type, int offset, int alignment, string? format, ref if (type.IsEnum) { var (dict, size, converter) = ReaderCache.GetEnumDictionaryAndValueSize(type); +#if NETSTANDARD2_0 + var rawValue = Shims.CreateReadOnlySpan(ref storage[offset], size); +#else var rawValue = MemoryMarshal.CreateReadOnlySpan(ref storage[offset], size); +#endif var name = dict.GetStringName(rawValue); if (name == null) { @@ -334,7 +348,12 @@ public bool TryReadTo(Type type, int offset, Utf8JsonWriter jsonWriter) if (type.IsEnum) { var (dict, size, converter) = ReaderCache.GetEnumDictionaryAndValueSize(type); - var rawValue = MemoryMarshal.CreateReadOnlySpan(ref storage[offset], size); + var rawValue = +#if NETSTANDARD2_0 + Shims.CreateReadOnlySpan(ref storage[offset], size); +#else + MemoryMarshal.CreateReadOnlySpan(ref storage[offset], size); +#endif var name = dict.GetJsonEncodedName(rawValue); if (name == null) { @@ -362,6 +381,39 @@ public bool TryReadTo(Type type, int offset, Utf8JsonWriter jsonWriter) return true; } + static bool IsSupportedType() + { +#if NETSTANDARD2_0 + var type = typeof(T); + var code = Type.GetTypeCode(type); + switch (code) + { + case TypeCode.Boolean: + case TypeCode.Byte: + case TypeCode.Char: + case TypeCode.DateTime: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.SByte: + case TypeCode.Single: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return true; + } + if (type.IsEnum || type == typeof(Guid) || type == typeof(DateTimeOffset)) + { + return true; + } + return false; +#else + return !RuntimeHelpers.IsReferenceOrContainsReferences(); +#endif + } + static void ThrowArgumentOutOfRangeException() { throw new ArgumentOutOfRangeException(); @@ -391,7 +443,7 @@ static class ReaderCache { static ReaderCache() { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) + if (!IsSupportedType()) { ReaderCache.readCache.TryAdd(typeof(T), null); return; diff --git a/src/ZLogger/Internal/Shims/DefaultInterpolatedStringHandler.cs b/src/ZLogger/Internal/Shims/DefaultInterpolatedStringHandler.cs index f064d055..89b522f0 100644 --- a/src/ZLogger/Internal/Shims/DefaultInterpolatedStringHandler.cs +++ b/src/ZLogger/Internal/Shims/DefaultInterpolatedStringHandler.cs @@ -7,7 +7,7 @@ namespace System.Runtime.CompilerServices; /// Provides a handler used by the language compiler to process interpolated strings into instances. [InterpolatedStringHandler] -public ref struct DefaultInterpolatedStringHandler +internal ref struct DefaultInterpolatedStringHandler { // Implementation note: // As this type lives in CompilerServices and is only intended to be targeted by the compiler, @@ -94,7 +94,7 @@ internal static int GetDefaultLength(int literalLength, int formattedCount) => /// Gets the built . /// The built string. - public override string ToString() => new string(Text); + public override string ToString() => Text.ToString(); /// Gets the built and clears the handler. /// The built string. @@ -106,7 +106,7 @@ internal static int GetDefaultLength(int literalLength, int formattedCount) => /// public string ToStringAndClear() { - string result = new string(Text); + string result = Text.ToString(); Clear(); return result; } @@ -627,7 +627,7 @@ private void GrowCore(uint requiredMinCapacity) // uint newCapacity = Math.Max(requiredMinCapacity, Math.Min((uint)_chars.Length * 2, string.MaxLength)); uint newCapacity = Math.Max(requiredMinCapacity, Math.Min((uint)_chars.Length * 2, 0x3FFFFFDF)); - int arraySize = (int)Math.Clamp(newCapacity, MinimumArrayPoolLength, int.MaxValue); + var arraySize = (int)Math.Max(Math.Min(newCapacity, int.MaxValue), MinimumArrayPoolLength); char[] newArray = ArrayPool.Shared.Rent(arraySize); _chars.Slice(0, _pos).CopyTo(newArray); diff --git a/src/ZLogger/Internal/Shims/Shims.NetStandard2.cs b/src/ZLogger/Internal/Shims/Shims.NetStandard2.cs new file mode 100644 index 00000000..bdee78bc --- /dev/null +++ b/src/ZLogger/Internal/Shims/Shims.NetStandard2.cs @@ -0,0 +1,36 @@ +#if NETSTANDARD2_0 +using System.Text; + +namespace ZLogger; + +internal static partial class Shims +{ + public static int GetBytes(this Encoding encoding, ReadOnlySpan chars, ReadOnlySpan bytes) + { + unsafe + { + fixed (char* charsPtr = &chars[0]) + fixed (byte* bytesPtr = &bytes[0]) + { + return encoding.GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length); + } + } + } + + public static unsafe Span CreateSpan(ref T value, int length) where T : unmanaged + { + fixed(T* pointer = &value) + { + return new Span(pointer, length); + } + } + + public static unsafe ReadOnlySpan CreateReadOnlySpan(ref T value, int length) where T : unmanaged + { + fixed(T* pointer = &value) + { + return new ReadOnlySpan(pointer, length); + } + } +} +#endif diff --git a/src/ZLogger/LogStates/InterpolatedStringLogState.cs b/src/ZLogger/LogStates/InterpolatedStringLogState.cs index 1e475593..46c923b9 100644 --- a/src/ZLogger/LogStates/InterpolatedStringLogState.cs +++ b/src/ZLogger/LogStates/InterpolatedStringLogState.cs @@ -93,7 +93,7 @@ public void WriteJsonParameterKeyValues(Utf8JsonWriter jsonWriter, JsonSerialize for (var i = 0; i < ParameterCount; i++) { ref var p = ref parameters[i]; - SystemTextJsonZLoggerFormatter.WriteMutatedJsonKeyName(p.Name, jsonWriter, keyNameMutator); + SystemTextJsonZLoggerFormatter.WriteMutatedJsonKeyName(p.Name.AsSpan(), jsonWriter, keyNameMutator); if (magicalBox.TryReadTo(p.Type, p.BoxOffset, jsonWriter)) { @@ -119,7 +119,7 @@ public ReadOnlySpan GetParameterKey(int index) public ReadOnlySpan GetParameterKeyAsString(int index) { - return parameters[index].Name; + return parameters[index].Name.AsSpan(); } public object? GetParameterValue(int index) diff --git a/src/ZLogger/LogStates/StringFormatterLogState.cs b/src/ZLogger/LogStates/StringFormatterLogState.cs index 7a867784..d543524b 100644 --- a/src/ZLogger/LogStates/StringFormatterLogState.cs +++ b/src/ZLogger/LogStates/StringFormatterLogState.cs @@ -74,7 +74,7 @@ public ReadOnlySpan GetParameterKeyAsString(int index) { if (originalStateParameters != null) { - return originalStateParameters[index].Key; + return originalStateParameters[index].Key.AsSpan(); } throw new IndexOutOfRangeException(nameof(index)); } diff --git a/src/ZLogger/ZLogger.csproj b/src/ZLogger/ZLogger.csproj index f0c10579..c8e959af 100644 --- a/src/ZLogger/ZLogger.csproj +++ b/src/ZLogger/ZLogger.csproj @@ -1,7 +1,7 @@  - netstandard2.1;net6.0;net8.0 + netstandard2.0;netstandard2.1;net6.0;net8.0 enable 12.0 enable @@ -24,14 +24,14 @@ - + - + diff --git a/src/ZLogger/ZLoggerInterpolatedStringHandler.cs b/src/ZLogger/ZLoggerInterpolatedStringHandler.cs index f448d6ce..92535273 100644 --- a/src/ZLogger/ZLoggerInterpolatedStringHandler.cs +++ b/src/ZLogger/ZLoggerInterpolatedStringHandler.cs @@ -285,9 +285,18 @@ public bool Equals(LiteralList other) // convert const strings as address sequence static ReadOnlySpan AsBytes(ReadOnlySpan literals) { +#if NETSTANDARD2_0 + if (literals.IsEmpty) + return default; + + return Shims.CreateSpan( + ref Unsafe.As(ref Unsafe.AsRef(in literals[0])), + literals.Length * Unsafe.SizeOf()); +#else return MemoryMarshal.CreateSpan( ref Unsafe.As(ref MemoryMarshal.GetReference(literals)), literals.Length * Unsafe.SizeOf()); +#endif } } }