-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathLiteralConverterRegistry.cs
189 lines (167 loc) · 7.15 KB
/
LiteralConverterRegistry.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using RootNamespace.Serialization.Literals.Converters;
using Yardarm.Client.Internal;
namespace RootNamespace.Serialization.Literals;
/// <summary>
/// Registry for <see cref="LiteralConverter{T}"/> instances.
/// </summary>
/// <remarks>
/// This type is thread-safe for reads (Get/TryGet), but not for writes (AddValueType/AddReferenceType). It is recommended to set up the registry once and then only read from it.
/// </remarks>
public sealed class LiteralConverterRegistry
{
private static LiteralConverterRegistry? s_instance;
/// <summary>
/// Singleton instance of the registry.
/// </summary>
public static LiteralConverterRegistry Instance
{
get
{
var instance = s_instance;
if (instance is not null)
{
return instance;
}
// In case two threads are getting Instance for the first time at the same time,
// use CompareExchange. One of the threads will not set the value, discarding the
// CreateDefaultRegistry result, and both calls will get the same value.
return Interlocked.CompareExchange(ref s_instance, CreateDefaultRegistry(), null) ?? s_instance;
}
set
{
ThrowHelper.ThrowIfNull(value);
Volatile.Write(ref s_instance, value);
}
}
private readonly Dictionary<Type, LiteralConverter> _converters = new();
// A LiteralConverterRegistry is generally initialized once and then read many times, so using a FrozenDictionary
// for reads becomes worthwhile for the slightly faster read performance.
private FrozenDictionary<Type, LiteralConverter>? _frozenConverters;
private FrozenDictionary<Type, LiteralConverter> FrozenConverters
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
FrozenDictionary<Type, LiteralConverter>? converters = _frozenConverters;
if (converters is not null)
{
return converters;
}
return Interlocked.CompareExchange(ref _frozenConverters, _converters.ToFrozenDictionary(), null) ?? _frozenConverters;
}
}
/// <summary>
/// Gets a registered converter for the specified type.
/// </summary>
/// <typeparam name="T">Converter to get.</typeparam>
/// <returns>The registered converter.</returns>
/// <exception cref="KeyNotFoundException">No converter is registered for the type.</exception>
public LiteralConverter<T> Get<T>()
{
if (TryGet(out LiteralConverter<T>? converter))
{
return converter;
}
ThrowHelper.ThrowKeyNotFoundException();
return null!;
}
/// <summary>
/// Gets a registered converter for the specified type.
/// </summary>
/// <typeparam name="T">Converter to get.</typeparam>
/// <param name="converter">The registered converter, if found.</param>
/// <returns>True if the converter was found, otherwise false.</returns>
public bool TryGet<T>([NotNullWhen(true)] out LiteralConverter<T>? converter)
{
if (FrozenConverters.TryGetValue(typeof(T), out LiteralConverter? baseConverter))
{
converter = (LiteralConverter<T>)baseConverter;
return true;
}
converter = null;
return false;
}
/// <summary>
/// Register a custom <see cref="LiteralConverter{T}"/> for a value type. Takes precedence over built-in converters and previously registered converters.
/// </summary>
/// <typeparam name="T">Type to register.</typeparam>
/// <param name="converter">Converter for the type.</param>
/// <param name="registerNullable">If true, automatically register a converter for <see cref="Nullable{T}"/> as well.</param>
/// <returns>The <see cref="LiteralConverterRegistry"/> for chaining.</returns>
public LiteralConverterRegistry Add<T>(LiteralConverter<T> converter, bool registerNullable = true)
where T : struct
{
ThrowHelper.ThrowIfNull(converter);
Debug.Assert(typeof(T).IsValueType);
_converters[typeof(T)] = converter;
if (registerNullable && Nullable.GetUnderlyingType(typeof(T)) == null)
{
// registerNullable is true and T is not already a nullable type
var nullableConverter = new NullableLiteralConverter<T>(converter);
_converters[typeof(T?)] = nullableConverter;
}
_frozenConverters = null; // Clear the frozen dictionary to force a rebuild
return this;
}
/// <summary>
/// Register a custom <see cref="LiteralConverter{T}"/> for a reference type. Takes precedence over built-in converters and previously registered converters.
/// </summary>
/// <typeparam name="T">Type to register.</typeparam>
/// <param name="converter">Converter for the type.</param>
/// <returns>The <see cref="LiteralConverterRegistry"/> for chaining.</returns>
public LiteralConverterRegistry Add<T>(LiteralConverter<T?> converter)
where T : class?
{
ThrowHelper.ThrowIfNull(converter);
Debug.Assert(!typeof(T).IsValueType);
_converters[typeof(T)] = converter;
_frozenConverters = null; // Clear the frozen dictionary to force a rebuild
return this;
}
/// <summary>
/// Returns a new <see cref="LiteralConverterRegistry"/> with the built-in converters registered.
/// </summary>
/// <returns>A new <see cref="LiteralConverterRegistry"/>.</returns>
public static LiteralConverterRegistry CreateBasicRegistry() =>
new LiteralConverterRegistry()
.Add(new BooleanLiteralConverter())
.Add(new DateTimeLiteralConverter())
.Add(new DateTimeOffsetLiteralConverter())
.Add(new TimeSpanLiteralConverter())
.Add(new GuidLiteralConverter())
.Add(new ByteLiteralConverter())
.Add(new SByteLiteralConverter())
.Add(new Int16LiteralConverter())
.Add(new UInt16LiteralConverter())
.Add(new Int32LiteralConverter())
.Add(new UInt32LiteralConverter())
.Add(new Int64LiteralConverter())
.Add(new UInt64LiteralConverter())
.Add(new FloatLiteralConverter())
.Add(new DoubleLiteralConverter())
.Add(new DecimalLiteralConverter())
.Add(new StringLiteralConverter())
.Add(new UriLiteralConverter())
#if NET6_0_OR_GREATER
.Add(new DateOnlyLiteralConverter())
.Add(new TimeOnlyLiteralConverter())
#endif
;
/// <summary>
/// Returns a new <see cref="LiteralConverterRegistry"/> with the all default converters registered.
/// </summary>
/// <returns>A new <see cref="LiteralConverterRegistry"/>.</returns>
public static LiteralConverterRegistry CreateDefaultRegistry()
{
LiteralConverterRegistry registry = CreateBasicRegistry();
// Enrichment point
return registry;
}
}