Skip to content

Commit a109679

Browse files
committed
Fixed GeneratedTypes caching (#84)
1 parent 9233d5c commit a109679

File tree

4 files changed

+68
-12
lines changed

4 files changed

+68
-12
lines changed

src-console/ConsoleApp1/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class Program
1414
{
1515
public static void Main(string[] args)
1616
{
17-
var emps = new Employee[] { new Employee { Name = "a" }, new Employee { Name = "b" } }.AsQueryable();
17+
var emps = new[] { new Employee { Name = "a" }, new Employee { Name = "b" } }.AsQueryable();
1818

1919
// normal
2020
Expression<Func<Employee, string>> exp = u => u.Name;

src-console/System.Linq.Dynamic.Core.ConsoleTestApp/Program.cs

+7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ public static void Main(string[] args)
3838

3939
DynamicProperty[] props = { new DynamicProperty("Name", typeof(string)), new DynamicProperty("Birthday", typeof(DateTime)) };
4040
Type type = DynamicClassFactory.CreateType(props);
41+
42+
DynamicProperty[] props2 = { new DynamicProperty("Name", typeof(string)), new DynamicProperty("Birthday", typeof(DateTime)) };
43+
Type type2 = DynamicClassFactory.CreateType(props2);
44+
45+
DynamicProperty[] props3 = { new DynamicProperty("Name", typeof(int)), new DynamicProperty("Birthday", typeof(DateTime)) };
46+
Type type3 = DynamicClassFactory.CreateType(props3);
47+
4148
DynamicClass dynamicClass = Activator.CreateInstance(type) as DynamicClass;
4249
dynamicClass.SetDynamicPropertyValue("Name", "Albert");
4350
dynamicClass.SetDynamicPropertyValue("Birthday", new DateTime(1879, 3, 14));

src/System.Linq.Dynamic.Core/DynamicClassFactory.cs

+21-11
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace System.Linq.Dynamic.Core
2121
public static class DynamicClassFactory
2222
{
2323
// EmptyTypes is used to indicate that we are looking for someting without any parameters.
24-
private readonly static Type[] EmptyTypes = new Type[0];
24+
private static readonly Type[] EmptyTypes = new Type[0];
2525

2626
private static readonly ConcurrentDictionary<string, Type> GeneratedTypes = new ConcurrentDictionary<string, Type>();
2727

@@ -98,24 +98,20 @@ public static Type CreateType([NotNull] IList<DynamicProperty> properties, bool
9898
{
9999
Check.HasNoNulls(properties, nameof(properties));
100100

101-
var types = properties.Select(p => p.Type).ToArray();
102-
var names = properties.Select(p => p.Name).ToArray();
101+
Type[] types = properties.Select(p => p.Type).ToArray();
102+
string[] names = properties.Select(p => p.Name).ToArray();
103103

104-
// Anonymous classes are generics based. The generic classes are distinguished by number of parameters and name of parameters.
105-
// The specific types of the parameters are the generic arguments.
106-
// We recreate this by creating a fullName composed of all the property names, separated by a "|".
107-
string fullName = string.Join("|", names.Select(Escape).ToArray());
104+
string key = GenerateKey(properties, createParameterCtor);
108105

109106
Type type;
110-
111-
if (!GeneratedTypes.TryGetValue(fullName, out type))
107+
if (!GeneratedTypes.TryGetValue(key, out type))
112108
{
113109
// We create only a single class at a time, through this lock
114110
// Note that this is a variant of the double-checked locking.
115111
// It is safe because we are using a thread safe class.
116112
lock (GeneratedTypes)
117113
{
118-
if (!GeneratedTypes.TryGetValue(fullName, out type))
114+
if (!GeneratedTypes.TryGetValue(key, out type))
119115
{
120116
int index = Interlocked.Increment(ref _index);
121117

@@ -350,7 +346,7 @@ public static Type CreateType([NotNull] IList<DynamicProperty> properties, bool
350346

351347
type = tb.CreateType();
352348

353-
type = GeneratedTypes.GetOrAdd(fullName + "|_" + (createParameterCtor ? "1" : "0"), type);
349+
type = GeneratedTypes.GetOrAdd(key, type);
354350
}
355351
}
356352
}
@@ -363,6 +359,20 @@ public static Type CreateType([NotNull] IList<DynamicProperty> properties, bool
363359
return type;
364360
}
365361

362+
/// <summary>
363+
/// Generates the key.
364+
/// Anonymous classes are generics based. The generic classes are distinguished by number of parameters and name of parameters. The specific types of the parameters are the generic arguments.
365+
/// </summary>
366+
/// <param name="dynamicProperties">The dynamic propertys.</param>
367+
/// <param name="createParameterCtor">if set to <c>true</c> [create parameter ctor].</param>
368+
/// <returns></returns>
369+
private static string GenerateKey(IEnumerable<DynamicProperty> dynamicProperties, bool createParameterCtor)
370+
{
371+
// We recreate this by creating a fullName composed of all the property names and types, separated by a "|".
372+
// And append and extra field depending on createParameterCtor.
373+
return string.Format("{0}_{1}", string.Join("|", dynamicProperties.Select(p => Escape(p.Name) + "~" + p.Type.FullName).ToArray()), createParameterCtor ? "c" : string.Empty);
374+
}
375+
366376
private static string Escape(string str)
367377
{
368378
// We escape the \ with \\, so that we can safely escape the "|" (that we use as a separator) with "\|"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Collections.Concurrent;
2+
using System.Threading;
3+
4+
namespace System.Linq.Dynamic.Core
5+
{
6+
internal class GeneratedTypesCache
7+
{
8+
private static int _index = -1;
9+
10+
private static readonly ConcurrentDictionary<string, Type> GeneratedTypes = new ConcurrentDictionary<string, Type>();
11+
12+
internal bool TryGet(string[] propertyNames, out Type type)
13+
{
14+
type = null;
15+
// Anonymous classes are generics based. The generic classes are distinguished by number of parameters and name of parameters.
16+
// The specific types of the parameters are the generic arguments.
17+
// We recreate this by creating a fullName composed of all the property names, separated by a "|".
18+
string fullName = string.Join("|", propertyNames.Select(Escape).ToArray());
19+
20+
lock (GeneratedTypes)
21+
{
22+
if (!GeneratedTypes.TryGetValue(fullName, out type))
23+
{
24+
int index = Interlocked.Increment(ref _index);
25+
}
26+
}
27+
28+
return true;
29+
}
30+
31+
private static string Escape(string str)
32+
{
33+
// We escape the \ with \\, so that we can safely escape the "|" (that we use as a separator) with "\|"
34+
str = str.Replace(@"\", @"\\");
35+
str = str.Replace(@"|", @"\|");
36+
return str;
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)