Skip to content

Commit 7d99e52

Browse files
committed
.net48..net46 compatibility
1 parent 851e2cd commit 7d99e52

File tree

7 files changed

+172
-9
lines changed

7 files changed

+172
-9
lines changed

FastCloner/Code/AhoCorasick.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,18 @@ private void BuildFailureLinks()
5151
{
5252
Node current = queue.Dequeue();
5353

54-
foreach ((char character, Node child) in current.Children)
54+
foreach (KeyValuePair<char, Node> kvp in current.Children)
5555
{
56-
queue.Enqueue(child);
56+
queue.Enqueue(kvp.Value);
5757

5858
Node? failure = current.Failure;
5959

60-
while (failure != null && !failure.Children.ContainsKey(character))
60+
while (failure != null && !failure.Children.ContainsKey(kvp.Key))
6161
{
6262
failure = failure.Failure;
6363
}
6464

65-
child.Failure = failure?.Children.GetValueOrDefault(character) ?? root;
65+
kvp.Value.Failure = failure?.Children.GetValueOrDefault(kvp.Key) ?? root;
6666
}
6767
}
6868
}

FastCloner/Code/Extensions.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
namespace FastCloner.Code;
2+
3+
internal static class Extensions
4+
{
5+
#if MODERN
6+
7+
#else
8+
public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = default(TValue))
9+
{
10+
return dictionary.TryGetValue(key, out TValue value) ? value : defaultValue;
11+
}
12+
13+
public static void Fill<T>(this T[] array, T value)
14+
{
15+
for (int i = 0; i < array.Length; i++)
16+
{
17+
array[i] = value;
18+
}
19+
}
20+
21+
public static void Fill<T>(this T[] array, T value, int startIndex, int count)
22+
{
23+
if (array == null)
24+
throw new ArgumentNullException(nameof(array));
25+
if (startIndex < 0 || startIndex >= array.Length)
26+
throw new ArgumentOutOfRangeException(nameof(startIndex));
27+
if (count < 0 || startIndex + count > array.Length)
28+
throw new ArgumentOutOfRangeException(nameof(count));
29+
30+
for (int i = startIndex; i < startIndex + count; i++)
31+
{
32+
array[i] = value;
33+
}
34+
}
35+
36+
public static void Fill(this Array array, object value)
37+
{
38+
if (array == null)
39+
throw new ArgumentNullException(nameof(array));
40+
41+
for (int i = 0; i < array.Length; i++)
42+
{
43+
array.SetValue(value, i);
44+
}
45+
}
46+
47+
public static void Fill(this Array array, object value, int startIndex, int count)
48+
{
49+
if (array == null)
50+
throw new ArgumentNullException(nameof(array));
51+
if (startIndex < 0 || startIndex >= array.Length)
52+
throw new ArgumentOutOfRangeException(nameof(startIndex));
53+
if (count < 0 || startIndex + count > array.Length)
54+
throw new ArgumentOutOfRangeException(nameof(count));
55+
56+
for (int i = startIndex; i < startIndex + count; i++)
57+
{
58+
array.SetValue(value, i);
59+
}
60+
}
61+
#endif
62+
}

FastCloner/Code/FastCloneState.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,11 @@ private static int ExpandPrime(int oldSize)
154154
private void Initialize(int size)
155155
{
156156
buckets = new int[size];
157+
#if MODERN
157158
Array.Fill(buckets, -1);
159+
#else
160+
buckets.Fill(-1);
161+
#endif
158162
entries = new Entry[size];
159163
}
160164

@@ -189,7 +193,11 @@ public void Insert(object key, object value)
189193
private void Resize(int newSize)
190194
{
191195
int[] newBuckets = new int[newSize];
196+
#if MODERN
192197
Array.Fill(newBuckets, -1);
198+
#else
199+
newBuckets.Fill(-1);
200+
#endif
193201

194202
Entry[] newEntries = new Entry[newSize];
195203
Array.Copy(entries, newEntries, count);

FastCloner/Code/FastClonerExprGenerator.cs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.Collections;
22
using System.Collections.Concurrent;
3+
#if NET8_0_OR_GREATER
34
using System.Collections.Frozen;
5+
#endif
46
using System.Collections.ObjectModel;
57
using System.Dynamic;
68
using System.Linq.Expressions;
@@ -13,9 +15,14 @@ internal static class FastClonerExprGenerator
1315
internal static readonly ConcurrentDictionary<Type, Func<Type, bool, ExpressionPosition, object>> CustomTypeHandlers = [];
1416
private static readonly ConcurrentDictionary<FieldInfo, bool> readonlyFields = new ConcurrentDictionary<FieldInfo, bool>();
1517
private static readonly MethodInfo fieldSetMethod;
16-
private static readonly Lazy<MethodInfo> _isTypeIgnoredMethodInfo = new Lazy<MethodInfo>(() => typeof(FastClonerCache).GetMethod(nameof(FastClonerCache.IsTypeIgnored), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, [typeof(Type)])!, LazyThreadSafetyMode.ExecutionAndPublication);
18+
19+
#if MODERN
20+
private static readonly Lazy<MethodInfo> isTypeIgnoredMethodInfo = new Lazy<MethodInfo>(() => typeof(FastClonerCache).GetMethod(nameof(FastClonerCache.IsTypeIgnored), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, [typeof(Type)])!, LazyThreadSafetyMode.ExecutionAndPublication);
21+
#else
22+
private static readonly Lazy<MethodInfo> isTypeIgnoredMethodInfo = new Lazy<MethodInfo>(() => typeof(FastClonerCache).GetMethod(nameof(FastClonerCache.IsTypeIgnored), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(Type) }, null)!, LazyThreadSafetyMode.ExecutionAndPublication);
23+
#endif
1724

18-
internal static MethodInfo IsTypeIgnoredMethodInfo => _isTypeIgnoredMethodInfo.Value;
25+
internal static MethodInfo IsTypeIgnoredMethodInfo => isTypeIgnoredMethodInfo.Value;
1926

2027
static FastClonerExprGenerator()
2128
{
@@ -44,11 +51,61 @@ internal static void ForceSetField(FieldInfo field, object obj, object value)
4451
field.SetValue(obj, value);
4552
}
4653

54+
#if MODERN
4755
internal readonly record struct ExpressionPosition(int Depth, int Index)
4856
{
4957
public ExpressionPosition Next() => this with { Index = Index + 1 };
5058
public ExpressionPosition Nested() => new ExpressionPosition(Depth + 1, 0);
5159
}
60+
#else
61+
internal readonly struct ExpressionPosition : IEquatable<ExpressionPosition>
62+
{
63+
public int Depth { get; }
64+
public int Index { get; }
65+
66+
public ExpressionPosition(int depth, int index)
67+
{
68+
Depth = depth;
69+
Index = index;
70+
}
71+
72+
public ExpressionPosition Next() => new ExpressionPosition(Depth, Index + 1);
73+
public ExpressionPosition Nested() => new ExpressionPosition(Depth + 1, 0);
74+
75+
public bool Equals(ExpressionPosition other)
76+
{
77+
return Depth == other.Depth && Index == other.Index;
78+
}
79+
80+
public override bool Equals(object obj)
81+
{
82+
return obj is ExpressionPosition other && Equals(other);
83+
}
84+
85+
public override int GetHashCode()
86+
{
87+
unchecked
88+
{
89+
return (Depth * 397) ^ Index;
90+
}
91+
}
92+
93+
public static bool operator ==(ExpressionPosition left, ExpressionPosition right)
94+
{
95+
return left.Equals(right);
96+
}
97+
98+
public static bool operator !=(ExpressionPosition left, ExpressionPosition right)
99+
{
100+
return !left.Equals(right);
101+
}
102+
103+
public override string ToString()
104+
{
105+
return $"ExpressionPosition {{ Depth = {Depth}, Index = {Index} }}";
106+
}
107+
}
108+
#endif
52109

53110
private static LabelTarget CreateLoopLabel(ExpressionPosition position)
54111
{
@@ -62,13 +119,22 @@ private static LabelTarget CreateLoopLabel(ExpressionPosition position)
62119

63120
private delegate object ProcessMethodDelegate(Type type, bool unboxStruct, ExpressionPosition position);
64121

122+
#if MODERN
65123
private static readonly FrozenDictionary<Type, ProcessMethodDelegate> knownTypeProcessors =
66124
new Dictionary<Type, ProcessMethodDelegate>
67125
{
68126
[typeof(ExpandoObject)] = (_, _, position) => GenerateExpandoObjectProcessor(position),
69127
[typeof(HttpRequestOptions)] = (_, _, position) => GenerateHttpRequestOptionsProcessor(position),
70128
[typeof(Array)] = (type, _, _) => GenerateProcessArrayMethod(type),
71129
}.ToFrozenDictionary();
130+
#else
131+
private static readonly Dictionary<Type, ProcessMethodDelegate> knownTypeProcessors =
132+
new Dictionary<Type, ProcessMethodDelegate>
133+
{
134+
[typeof(ExpandoObject)] = (_, _, position) => GenerateExpandoObjectProcessor(position),
135+
[typeof(Array)] = (type, _, _) => GenerateProcessArrayMethod(type),
136+
};
137+
#endif
72138

73139
private static readonly AhoCorasick badTypes = new AhoCorasick([
74140
"Castle.Proxies.",
@@ -385,6 +451,7 @@ private static List<MemberInfo> GetAllMembers(Type type)
385451
return Expression.Lambda(funcType, Expression.Block(blockParams, expressionList), from, state).Compile();
386452
}
387453

454+
#if MODERN
388455
private static object GenerateHttpRequestOptionsProcessor(ExpressionPosition position)
389456
{
390457
if (FastClonerCache.IsTypeIgnored(typeof(HttpRequestOptions)))
@@ -418,6 +485,7 @@ private static object GenerateHttpRequestOptionsProcessor(ExpressionPosition pos
418485

419486
return Expression.Lambda<Func<object, FastCloneState, object>>(block, from, state).Compile();
420487
}
488+
#endif
421489

422490
private static object GenerateExpandoObjectProcessor(ExpressionPosition position)
423491
{
@@ -554,8 +622,13 @@ private static object GenerateDictionaryProcessor(Type dictType, Type keyType, T
554622
}
555623

556624
// For read-only collections
625+
#if MODERN
557626
bool isReadOnly = dictType.Name.Contains("ReadOnly", StringComparison.InvariantCultureIgnoreCase) ||
558627
(dictType.IsGenericType && dictType.GetGenericTypeDefinition() == typeof(ReadOnlyDictionary<,>));
628+
#else
629+
bool isReadOnly = dictType.Name.IndexOf("ReadOnly", StringComparison.InvariantCultureIgnoreCase) >= 0 ||
630+
(dictType.IsGenericType && dictType.GetGenericTypeDefinition() == typeof(ReadOnlyDictionary<,>));
631+
#endif
559632

560633
Type innerDictType = isReadOnly
561634
? typeof(Dictionary<,>).MakeGenericType(keyType, valueType)
@@ -1082,7 +1155,11 @@ private static object GenerateProcessSetMethod(Type type, ExpressionPosition pos
10821155

10831156
ParameterExpression local = Expression.Variable(type);
10841157

1158+
#if MODERN
10851159
bool isReadOnly = type.Name.Contains("ReadOnly", StringComparison.InvariantCultureIgnoreCase);
1160+
#else
1161+
bool isReadOnly = type.Name.IndexOf("ReadOnly", StringComparison.InvariantCultureIgnoreCase) >= 0;
1162+
#endif
10861163

10871164
// Use HashSet as inner collection
10881165
Type innerSetType = isReadOnly

FastCloner/Code/FastClonerSafeTypes.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,30 +31,38 @@ internal static class FastClonerSafeTypes
3131
[typeof(nint)] = true,
3232
[typeof(nuint)] = true,
3333
[typeof(Guid)] = true,
34+
#if MODERN
3435
[typeof(Rune)] = true,
36+
#endif
3537

3638
// Time-related types
3739
[typeof(TimeSpan)] = true,
3840
[typeof(TimeZoneInfo)] = true,
3941
[typeof(DateTime)] = true,
4042
[typeof(DateTimeOffset)] = true,
43+
#if MODERN
4144
[typeof(DateOnly)] = true,
4245
[typeof(TimeOnly)] = true,
46+
#endif
4347

4448
// Numeric types
49+
#if MODERN
4550
[typeof(Half)] = true,
4651
[typeof(Int128)] = true,
4752
[typeof(UInt128)] = true,
4853
[typeof(Complex)] = true,
49-
54+
#endif
5055
// Others
5156
[typeof(DBNull)] = true,
5257
[StringComparer.Ordinal.GetType()] = true,
5358
[StringComparer.OrdinalIgnoreCase.GetType()] = true,
5459
[StringComparer.InvariantCulture.GetType()] = true,
5560
[StringComparer.InvariantCultureIgnoreCase.GetType()] = true,
61+
62+
#if MODERN
5663
[typeof(Range)] = true,
5764
[typeof(Index)] = true
65+
#endif
5866
};
5967

6068
private static readonly ConcurrentDictionary<Type, bool> knownTypes = [];

FastCloner/FastCloner.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ public static void IgnoreType(Type type)
6363
/// </summary>
6464
public static HashSet<Type> GetIgnoredTypes()
6565
{
66+
#if MODERN
6667
return FastClonerCache.AlwaysIgnoredTypes.Keys.ToHashSet();
68+
#else
69+
return [..FastClonerCache.AlwaysIgnoredTypes.Keys];
70+
#endif
6771
}
6872

6973
/// <summary>

FastCloner/FastCloner.csproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
4+
<TargetFrameworks>net8.0;net46;net472;net48</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<LangVersion>preview</LangVersion>
@@ -20,7 +20,11 @@
2020
<RepositoryType>git</RepositoryType>
2121
<PackageTags>deepclone,clone,fastclone,cloner,replicate,duplicate</PackageTags>
2222
</PropertyGroup>
23-
23+
24+
<PropertyGroup>
25+
<DefineConstants Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">$(DefineConstants);MODERN</DefineConstants>
26+
</PropertyGroup>
27+
2428
<PropertyGroup>
2529
<Version>3.3.6</Version>
2630

0 commit comments

Comments
 (0)