Skip to content

Commit 286f34b

Browse files
release v3.2.5
- [feat] support deserialize to collection virtual interfaces (IList, IDictionary, Dictionary<int, IDictionary<X,Y>[]>, etc.)
1 parent 7c597d6 commit 286f34b

File tree

3 files changed

+132
-24
lines changed

3 files changed

+132
-24
lines changed

src/Nino.Generator/Collection/CollectionDeserializerGenerator.cs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ protected override void Generate(SourceProductionContext spc)
2020
HashSet<string> addedElemType = new HashSet<string>();
2121
foreach (var type in typeSymbols)
2222
{
23-
//obviously we cannot deserialize to an interface, we want an actual type
24-
if (type.TypeKind == TypeKind.Interface) continue;
25-
2623
var typeFullName = type.ToDisplayString();
2724
if (!addedType.Add(typeFullName)) continue;
2825

@@ -56,21 +53,37 @@ protected override void Generate(SourceProductionContext spc)
5653
//if type implements IDictionary only
5754
var idict = type.AllInterfaces.FirstOrDefault(namedTypeSymbol =>
5855
namedTypeSymbol.Name == "IDictionary" && namedTypeSymbol.TypeArguments.Length == 2);
56+
if (idict == null)
57+
{
58+
//if type is indeed IDictionary
59+
idict = type.TypeKind == TypeKind.Interface && type.Name == "IDictionary"
60+
? (INamedTypeSymbol)type
61+
: null;
62+
}
63+
5964
if (idict != null)
6065
{
6166
if (idict is { TypeArguments.Length: 2 } namedTypeSymbol)
6267
{
6368
var type1 = namedTypeSymbol.TypeArguments[0].ToDisplayString();
6469
var type2 = namedTypeSymbol.TypeArguments[1].ToDisplayString();
65-
sb.AppendLine(GenerateDictionarySerialization(type1, type2, typeFullName, " "));
70+
sb.AppendLine(GenerateDictionarySerialization(type1, type2,
71+
namedTypeSymbol.TypeArguments[0], namedTypeSymbol.TypeArguments[1],
72+
typeFullName,
73+
type.TypeKind == TypeKind.Interface
74+
? "System.Collections.Generic.Dictionary<" + type1 + ", " + type2 + ">"
75+
: typeFullName, " "));
6676
sb.GenerateClassDeserializeMethods(typeFullName);
77+
6778
continue;
6879
}
6980
}
7081

7182
//if type is array
7283
if (type is IArrayTypeSymbol)
7384
{
85+
if (((IArrayTypeSymbol)type).ElementType.IsUnmanagedType)
86+
continue;
7487
var elemType = ((IArrayTypeSymbol)type).ElementType.ToDisplayString();
7588
if (addedElemType.Add(elemType))
7689
sb.AppendLine(GenerateArraySerialization(
@@ -81,7 +94,8 @@ protected override void Generate(SourceProductionContext spc)
8194
}
8295

8396
//ICollection<T>
84-
if (type is INamedTypeSymbol { TypeArguments.Length: 1 } s)
97+
if (type is INamedTypeSymbol { TypeArguments.Length: 1 } s &&
98+
s.AllInterfaces.Any(namedTypeSymbol => namedTypeSymbol.Name == "ICollection"))
8599
{
86100
var elemType = s.TypeArguments[0].ToDisplayString();
87101
if (addedElemType.Add(elemType) && !s.TypeArguments[0].IsUnmanagedType)
@@ -183,12 +197,20 @@ public static void Deserialize(out {{elemType}}[] value, ref Reader reader)
183197
return $"{indent}{ret}";
184198
}
185199

186-
private static string GenerateDictionarySerialization(string type1, string type2, string typeFullName,
200+
private static string GenerateDictionarySerialization(string type1, string type2, ITypeSymbol keyType,
201+
ITypeSymbol valType,
202+
string sigTypeFullName,
203+
string typeFullName,
187204
string indent = "")
188205
{
206+
bool isUnmanaged = keyType.IsUnmanagedType && valType.IsUnmanagedType;
207+
var reader = isUnmanaged ? "reader" : "eleReader";
208+
var slice = isUnmanaged ? "" : "eleReader = reader.Slice();";
209+
var eleReaderDecl = isUnmanaged ? "" : "Reader eleReader;";
210+
189211
var ret = $$"""
190212
[MethodImpl(MethodImplOptions.AggressiveInlining)]
191-
public static void Deserialize(out {{typeFullName}} value, ref Reader reader)
213+
public static void Deserialize(out {{sigTypeFullName}} value, ref Reader reader)
192214
{
193215
#if {{NinoTypeHelper.WeakVersionToleranceSymbol}}
194216
if (reader.Eof)
@@ -204,13 +226,13 @@ public static void Deserialize(out {{typeFullName}} value, ref Reader reader)
204226
return;
205227
}
206228
207-
Reader eleReader;
229+
{{eleReaderDecl}}
208230
209231
value = new {{typeFullName}}({{(typeFullName.StartsWith("System.Collections.Generic.Dictionary") ? "length" : "")}});
210232
for (int i = 0; i < length; i++)
211233
{
212-
eleReader = reader.Slice();
213-
Deserialize(out KeyValuePair<{{type1}}, {{type2}}> kvp, ref eleReader);
234+
{{slice}}
235+
Deserialize(out KeyValuePair<{{type1}}, {{type2}}> kvp, ref {{reader}});
214236
value[kvp.Key] = kvp.Value;
215237
}
216238
}
@@ -221,7 +243,8 @@ public static void Deserialize(out {{typeFullName}} value, ref Reader reader)
221243
return $"{indent}{ret}";
222244
}
223245

224-
private static string GenerateCollectionSerialization(string prefix, string elemType, string sigTypeFullname,
246+
private static string GenerateCollectionSerialization(string prefix, string elemType,
247+
string sigTypeFullname,
225248
string typeFullname,
226249
string indent)
227250
{
@@ -253,7 +276,8 @@ public static void Deserialize(out {{sigTypeFullname}} value, ref Reader reader)
253276
return $"{indent}{ret}";
254277
}
255278

256-
private static string GenerateListSerialization(string prefix, string elemType, string sigTypeFullname,
279+
private static string GenerateListSerialization(string prefix, string elemType,
280+
string sigTypeFullname,
257281
string typeFullname,
258282
string indent,
259283
bool isInterface = false)

src/Nino.Generator/NinoTypeHelper.cs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,6 @@ bool IsAccessibleType(ITypeSymbol typeSymbol)
9292
return true;
9393
}
9494

95-
//we dont want IList/ICollection of unmanaged
96-
var i = ts.AllInterfaces.FirstOrDefault(namedTypeSymbol =>
97-
namedTypeSymbol.Name == (forDeserialization ? "IList" : "ICollection") &&
98-
namedTypeSymbol.TypeArguments.Length == 1);
99-
if (i != null)
100-
{
101-
if (i.TypeArguments[0].IsUnmanagedType) return false;
102-
if (!IsAccessibleType(i.TypeArguments[0])) return false;
103-
}
104-
10595
//we dont want Dictionary that has no getter/setter in its indexer
10696
var iDict = ts.AllInterfaces.FirstOrDefault(namedTypeSymbol =>
10797
namedTypeSymbol.Name == "IDictionary" && namedTypeSymbol.TypeArguments.Length == 2);
@@ -110,9 +100,22 @@ bool IsAccessibleType(ITypeSymbol typeSymbol)
110100
var kType = iDict.TypeArguments[0];
111101
var vType = iDict.TypeArguments[1];
112102

113-
if (kType.IsUnmanagedType && vType.IsUnmanagedType) return false;
103+
bool isJustTrivial = ts.OriginalDefinition.ToDisplayString() ==
104+
"System.Collections.Generic.Dictionary<TKey, TValue>" ||
105+
ts.OriginalDefinition.ToDisplayString() ==
106+
"System.Collections.Generic.IDictionary<TKey, TValue>" ||
107+
ts.TypeKind == TypeKind.Interface;
108+
109+
if (kType.IsUnmanagedType && vType.IsUnmanagedType)
110+
{
111+
if (forDeserialization && !isJustTrivial) return true;
112+
return false;
113+
}
114+
114115
if (!IsAccessibleType(kType) || !IsAccessibleType(vType)) return false;
115116

117+
if (ts.TypeKind == TypeKind.Interface && forDeserialization) return true;
118+
116119
//use indexer to set/get value, TODO alternatively, use attributes to specify relevant methods
117120
var indexers = ts
118121
.GetMembers()
@@ -135,7 +138,7 @@ bool IsAccessibleType(ITypeSymbol typeSymbol)
135138
if (!hasValidIndexer)
136139
return false;
137140
}
138-
141+
139142
//we dont want array of unmanaged
140143
if (ts is IArrayTypeSymbol arrayTypeSymbol)
141144
{
@@ -144,6 +147,27 @@ bool IsAccessibleType(ITypeSymbol typeSymbol)
144147
if (!IsAccessibleType(arrayTypeSymbol.ElementType)) return false;
145148
}
146149

150+
//we dont want IList/ICollection of unmanaged
151+
var i = ts.AllInterfaces.FirstOrDefault(namedTypeSymbol =>
152+
namedTypeSymbol.Name == (forDeserialization ? "IList" : "ICollection") &&
153+
namedTypeSymbol.TypeArguments.Length == 1);
154+
if (i != null)
155+
{
156+
bool isJustTrivial = ts.OriginalDefinition.ToDisplayString() ==
157+
"System.Collections.Generic.List<T>" ||
158+
ts.OriginalDefinition.ToDisplayString() ==
159+
"System.Collections.Generic.IList<T>" ||
160+
ts.TypeKind == TypeKind.Interface;
161+
if (i.TypeArguments[0].IsUnmanagedType)
162+
{
163+
if (forDeserialization && !isJustTrivial) return true;
164+
return false;
165+
}
166+
167+
if (!IsAccessibleType(i.TypeArguments[0])) return false;
168+
}
169+
170+
147171
//we dont want nullable of unmanaged
148172
if (ts.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
149173
{

src/Nino.UnitTests/SimpleTests.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,66 @@ namespace Nino.UnitTests
1010
[TestClass]
1111
public class SimpleTests
1212
{
13+
[TestMethod]
14+
public void TestCollections()
15+
{
16+
ConcurrentDictionary<int,int>[] dict = new ConcurrentDictionary<int, int>[10];
17+
for (int i = 0; i < dict.Length; i++)
18+
{
19+
dict[i] = new ConcurrentDictionary<int, int>();
20+
dict[i].TryAdd(i, i);
21+
}
22+
var bytes = dict.Serialize();
23+
Assert.IsNotNull(bytes);
24+
25+
Deserializer.Deserialize(bytes, out ConcurrentDictionary<int, int>[] result);
26+
Assert.AreEqual(dict.Length, result.Length);
27+
for (int i = 0; i < dict.Length; i++)
28+
{
29+
Assert.AreEqual(dict[i].Count, result[i].Count);
30+
Assert.AreEqual(dict[i][i], result[i][i]);
31+
}
32+
33+
ConcurrentDictionary<int,string>[] dict2 = new ConcurrentDictionary<int, string>[10];
34+
for (int i = 0; i < dict2.Length; i++)
35+
{
36+
dict2[i] = new ConcurrentDictionary<int, string>();
37+
dict2[i].TryAdd(i, i.ToString());
38+
}
39+
bytes = dict2.Serialize();
40+
Assert.IsNotNull(bytes);
41+
42+
Deserializer.Deserialize(bytes, out IDictionary<int, string>[] result2);
43+
Assert.AreEqual(dict2.Length, result2.Length);
44+
for (int i = 0; i < dict2.Length; i++)
45+
{
46+
Assert.AreEqual(dict2[i].Count, result2[i].Count);
47+
Assert.AreEqual(dict2[i][i], result2[i][i]);
48+
}
49+
50+
IDictionary<int, IDictionary<int, int[]>> dict3 = new Dictionary<int, IDictionary<int, int[]>>();
51+
for (int i = 0; i < 10; i++)
52+
{
53+
dict3[i] = new ConcurrentDictionary<int, int[]>();
54+
dict3[i].TryAdd(i, new int[]{i, i});
55+
}
56+
dict2.Serialize();
57+
bytes = dict3.Serialize();
58+
Assert.IsNotNull(bytes);
59+
60+
Deserializer.Deserialize(bytes, out IDictionary<int, IDictionary<int, int[]>> result3);
61+
Assert.AreEqual(dict3.Count, result3.Count);
62+
for (int i = 0; i < dict3.Count; i++)
63+
{
64+
Assert.AreEqual(dict3[i].Count, result3[i].Count);
65+
Assert.AreEqual(dict3[i][i].Length, result3[i][i].Length);
66+
for (int j = 0; j < dict3[i][i].Length; j++)
67+
{
68+
Assert.AreEqual(dict3[i][i][j], result3[i][i][j]);
69+
}
70+
}
71+
}
72+
1373
[TestMethod]
1474
public void TestModifyListMemberDataStructure()
1575
{

0 commit comments

Comments
 (0)