Newer
Older
using System.Runtime.CompilerServices;
// from https://stackoverflow.com/questions/31918924/expression-tree-to-initialize-new-anonymous-object-with-arbitrary-number-of-prop
namespace REST_JSON_API
{
public interface ITupleFactory
{
public Type TupleType { get; }
public NewExpression MakeNewExpression(IEnumerable<Expression> arguments);
}
/// <summary>
/// Creates types that are much like anonymous types.
/// </summary>
public static class TupleFactory
{
// the dynamic module used to emit new types
//This code hast Build problems with for example .NET Standard 2.1: private static readonly ModuleBuilder _module = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName { Name = nameof(TupleFactory) }, AssemblyBuilderAccess.Run).DefineDynamicModule(nameof(TupleFactory), false);
//PLEASE USE .NET FRAMEWORK for building this script.
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
private static readonly ModuleBuilder _module = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName { Name = nameof(TupleFactory) }, AssemblyBuilderAccess.Run).DefineDynamicModule(nameof(TupleFactory), false);
// the generic type definitions constructed so far
private static readonly Dictionary<ICollection<string>, Type> _genericTypeDefinitions = new Dictionary<ICollection<string>, Type>(CollectionComparer<string>.Default);
// the new expression factory singletons constructed so far
private static readonly Dictionary<ICollection<KeyValuePair<string, Type>>, ITupleFactory> _newExpressionFactories = new Dictionary<ICollection<KeyValuePair<string, Type>>, ITupleFactory>(new CollectionComparer<KeyValuePair<string, Type>>(KeyValueComparer<string, Type>.Default));
// some reflection objects used
private static readonly ConstructorInfo _objectCtor = typeof(object).GetConstructor(Type.EmptyTypes);
private static readonly MethodInfo _objectEquals = typeof(object).GetMethod("Equals", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(object) }, null);
private static readonly MethodInfo _objectGetHashCode = typeof(object).GetMethod("GetHashCode", BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
private static readonly MethodInfo _objectToString = typeof(object).GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
private static readonly MethodInfo _stringFormat = typeof(string).GetMethod("Format", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string), typeof(object[]) }, null);
private static readonly MethodInfo _equalityComparerDefaultGetter;
private static readonly MethodInfo _equalityComparerEquals;
private static readonly MethodInfo _equalityComparerGetHashCode;
static TupleFactory()
{
// init more reflection objects
_equalityComparerDefaultGetter = typeof(EqualityComparer<>).GetProperty("Default", BindingFlags.Public | BindingFlags.Static).GetGetMethod();
var eqT = typeof(EqualityComparer<>).GetGenericArguments()[0];
_equalityComparerEquals = typeof(EqualityComparer<>).GetMethod("Equals", BindingFlags.Public | BindingFlags.Instance, null, new[] { eqT, eqT }, null);
_equalityComparerGetHashCode = typeof(EqualityComparer<>).GetMethod("GetHashCode", BindingFlags.Public | BindingFlags.Instance, null, new[] { eqT }, null);
}
/// <summary>
/// Gets a <see cref="ITupleFactory"/> singleton for a sequence of properties.
/// </summary>
/// <param name="properties">Name/Type pairs for the properties.</param>
public static ITupleFactory Create(IEnumerable<KeyValuePair<string, Type>> properties)
{
// check input
if (properties == null) throw new ArgumentNullException(nameof(properties));
var propList = properties.ToList();
if (propList.Select(p => p.Key).Distinct().Count() != propList.Count)
throw new ArgumentException("Property names must be distinct.");
lock (_module) // locks access to the static dictionaries
{
if (_newExpressionFactories.TryGetValue(propList, out ITupleFactory result)) // we already have it
return result;
var propertyNames = propList.Select(p => p.Key).ToList();
if (!_genericTypeDefinitions.TryGetValue(propertyNames, out Type genericTypeDefinition))
{
#region create new generic type definition
{
var typeBuilder = _module.DefineType($"<>f__AnonymousType{_newExpressionFactories.Count}`{propertyNames.Count}", TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit);
var genParams = propertyNames.Count > 0
? typeBuilder.DefineGenericParameters(propertyNames.Select(p => $"<{p}>j__TPar").ToArray())
: new GenericTypeParameterBuilder[0];
// attributes on type
var debuggerDisplay = "\\{ " + string.Join(", ", propertyNames.Select(n => $"{n} = {{{n}}}")) + " }";
// ReSharper disable AssignNullToNotNullAttribute
typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(DebuggerDisplayAttribute).GetConstructor(new[] { typeof(string) }), new object[] { debuggerDisplay }));
typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes), new object[0]));
// ReSharper restore AssignNullToNotNullAttribute
var fields = new List<FieldBuilder>();
var props = new List<PropertyBuilder>();
foreach (var name in propertyNames)
{
var genParam = genParams[fields.Count];
var field = typeBuilder.DefineField($"<{name}>i__Field", genParam, FieldAttributes.Private | FieldAttributes.InitOnly);
fields.Add(field);
var property = typeBuilder.DefineProperty(name, PropertyAttributes.None, genParam, null);
props.Add(property);
var getter = typeBuilder.DefineMethod($"get_{name}", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, genParam, Type.EmptyTypes);
var il = getter.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
il.Emit(OpCodes.Ret);
property.SetGetMethod(getter);
}
#region ctor
{
// ReSharper disable once CoVariantArrayConversion
var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, genParams);
var il = ctorBuilder.GetILGenerator();
// call base class ctor
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, _objectCtor);
// assign args to fields
for (var i = 0; i < fields.Count; i++)
{
il.Emit(OpCodes.Ldarg_0);
EmitLdarg(il, i + 1);
il.Emit(OpCodes.Stfld, fields[i]);
}
il.Emit(OpCodes.Ret);
}
#endregion
#region override Equals
{
var equals = typeBuilder.DefineMethod("Equals", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, typeof(bool), new[] { typeof(object) });
typeBuilder.DefineMethodOverride(equals, _objectEquals);
var il = equals.GetILGenerator();
il.DeclareLocal(typeBuilder);
var retFalse = il.DefineLabel();
var ret = il.DefineLabel();
// local = argument as (the type being constructed)?
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Isinst, typeBuilder);
il.Emit(OpCodes.Stloc_0);
// push result of the "as" operator
il.Emit(OpCodes.Ldloc_0);
foreach (var field in fields)
{
var comparer = typeof(EqualityComparer<>).MakeGenericType(field.FieldType);
var defaultGetter = TypeBuilder.GetMethod(comparer, _equalityComparerDefaultGetter);
var equalsMethod = TypeBuilder.GetMethod(comparer, _equalityComparerEquals);
// check if the result of the previous check is false
il.Emit(OpCodes.Brfalse, retFalse);
// push EqualityComparer<FieldType>.Default.Equals(this.field, other.field)
il.Emit(OpCodes.Call, defaultGetter);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldfld, field);
il.Emit(OpCodes.Callvirt, equalsMethod);
}
// jump to the end with what was the last result
il.Emit(OpCodes.Br_S, ret);
// push false
il.MarkLabel(retFalse);
il.Emit(OpCodes.Ldc_I4_0);
il.MarkLabel(ret);
il.Emit(OpCodes.Ret);
}
#endregion
#region override GetHashCode
{
var getHashCode = typeBuilder.DefineMethod("GetHashCode", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, typeof(int), Type.EmptyTypes);
typeBuilder.DefineMethodOverride(getHashCode, _objectGetHashCode);
var il = getHashCode.GetILGenerator();
// init result with seed
il.Emit(OpCodes.Ldc_I4, HashCode.Seed);
foreach (var field in fields)
{
var comparer = typeof(EqualityComparer<>).MakeGenericType(field.FieldType);
var defaultGetter = TypeBuilder.GetMethod(comparer, _equalityComparerDefaultGetter);
var getHashCodeMethod = TypeBuilder.GetMethod(comparer, _equalityComparerGetHashCode);
// hash so far * factor
il.Emit(OpCodes.Ldc_I4, HashCode.Factor);
il.Emit(OpCodes.Mul);
// ... + EqualityComparer<FieldType>.GetHashCode(field)
il.Emit(OpCodes.Call, defaultGetter);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
il.Emit(OpCodes.Callvirt, getHashCodeMethod);
il.Emit(OpCodes.Add);
}
il.Emit(OpCodes.Ret);
}
#endregion
#region override ToString
{
var toString = typeBuilder.DefineMethod("ToString", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, typeof(string), Type.EmptyTypes);
typeBuilder.DefineMethodOverride(toString, _objectToString);
var template = "{{ " + string.Join(", ", propertyNames.Select((n, i) => $"{n} = {{{i}}}")) + " }}";
var il = toString.GetILGenerator();
// push template
il.Emit(OpCodes.Ldstr, template);
// push new array
EmitLdc(il, fields.Count);
il.Emit(OpCodes.Newarr, typeof(object));
var index = 0;
foreach (var field in fields)
{
il.Emit(OpCodes.Dup); // duplicate array ref
EmitLdc(il, index); // push array index
// store boxed field in array
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
il.Emit(OpCodes.Box, field.FieldType);
il.Emit(OpCodes.Stelem, typeof(object));
index++;
}
il.Emit(OpCodes.Call, _stringFormat);
il.Emit(OpCodes.Ret);
}
#endregion
genericTypeDefinition = typeBuilder.CreateType();
}
#endregion
_genericTypeDefinitions.Add(propertyNames, genericTypeDefinition);
}
var type = propList.Count == 0 ? genericTypeDefinition : genericTypeDefinition.MakeGenericType(propList.Select(p => p.Value).ToArray());
result = new TupleFactoryImpl(type, propertyNames);
_newExpressionFactories.Add(propList, result);
return result;
}
}
/// <summary>
/// Gets a <see cref="NewExpression"/> for a tuple type with the specified properties.
/// </summary>
public static NewExpression MakeNewExpression(IEnumerable<KeyValuePair<string, Expression>> properties)
{
var props = properties.ToList();
var tupleFactory = Create(props.Select(p => new KeyValuePair<string, Type>(p.Key, p.Value.Type)));
return tupleFactory.MakeNewExpression(props.Select(p => p.Value));
}
private sealed class TupleFactoryImpl : ITupleFactory
{
public Type TupleType { get; }
private readonly ConstructorInfo _ctor;
private readonly MemberInfo[] _properties;
public TupleFactoryImpl(Type tupleType, IEnumerable<string> propertyNames)
{
TupleType = tupleType;
_ctor = tupleType.GetConstructors().Single();
var propsByName = tupleType.GetProperties().ToDictionary(p => p.Name);
_properties = propertyNames.Select(name => (MemberInfo)propsByName[name]).ToArray();
}
public NewExpression MakeNewExpression(IEnumerable<Expression> arguments)
{
return Expression.New(_ctor, arguments, _properties);
}
}
/// <summary>
/// Helper function to pick the optimal op code.
/// </summary>
private static void EmitLdarg(ILGenerator il, int index)
{
if (index < 0) throw new ArgumentOutOfRangeException();
switch (index)
{
case 0: il.Emit(OpCodes.Ldarg_0); break;
case 1: il.Emit(OpCodes.Ldarg_1); break;
case 2: il.Emit(OpCodes.Ldarg_2); break;
case 3: il.Emit(OpCodes.Ldarg_3); break;
default:
if (index <= byte.MaxValue)
il.Emit(OpCodes.Ldarg_S, (byte)index);
else if (index <= short.MaxValue)
il.Emit(OpCodes.Ldarg, (short)index);
else
throw new ArgumentOutOfRangeException();
break;
}
}
/// <summary>
/// Helper function to pick the optimal op code.
/// </summary>
private static void EmitLdc(ILGenerator il, int i)
{
switch (i)
{
case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
case 0: il.Emit(OpCodes.Ldc_I4_0); break;
case 1: il.Emit(OpCodes.Ldc_I4_1); break;
case 2: il.Emit(OpCodes.Ldc_I4_2); break;
case 3: il.Emit(OpCodes.Ldc_I4_3); break;
case 4: il.Emit(OpCodes.Ldc_I4_4); break;
case 5: il.Emit(OpCodes.Ldc_I4_5); break;
case 6: il.Emit(OpCodes.Ldc_I4_6); break;
case 7: il.Emit(OpCodes.Ldc_I4_7); break;
case 8: il.Emit(OpCodes.Ldc_I4_8); break;
default:
if (i >= byte.MinValue && i <= byte.MaxValue)
il.Emit(OpCodes.Ldc_I4_S, (byte)i);
else
il.Emit(OpCodes.Ldc_I4, i);
break;
}
}
}
/// <summary>
/// Compute a hash code.
/// </summary>
public struct HashCode
{
// magic numbers for hash code
public const int Seed = 0x20e699b;
public const int Factor = unchecked((int)0xa5555529);
private readonly int? _value;
private HashCode(int value)
{
_value = value;
}
/// <summary>
/// Convert to the actual hash code based on what was added so far.
/// </summary>
public static implicit operator int(HashCode hc) => hc._value ?? 0;
/// <summary>
/// Add a hash code to the state.
/// </summary>
/// <returns>An updated <see cref="HashCode"/>.</returns>
public static HashCode operator +(HashCode hc, int other) => new HashCode(unchecked((hc._value == null ? Seed : hc._value.Value * Factor) + other));
/// <summary>
/// Add a sequence of hash code to the state.
/// </summary>
/// <returns>An updated <see cref="HashCode"/>.</returns>
public static HashCode operator +(HashCode hc, IEnumerable<int> others) => others.Aggregate(hc, (a, c) => a + c);
}
/// <summary>
/// <see cref="IEqualityComparer{T}"/> for <see cref="KeyValuePair{TKey, TValue}"/>.
/// </summary>
public sealed class KeyValueComparer<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>
{
/// <summary>
/// Gets the singleton.
/// </summary>
public static KeyValueComparer<TKey, TValue> Default { get; } = new KeyValueComparer<TKey, TValue>();
private readonly IEqualityComparer<TKey> _keyComparer;
private readonly IEqualityComparer<TValue> _valueComparer;
/// <summary>
/// Initialize by specifying <see cref="IEqualityComparer{T}"/>s for key and value.
/// </summary>
public KeyValueComparer(IEqualityComparer<TKey> keyComparer = null, IEqualityComparer<TValue> valueComparer = null)
{
_keyComparer = keyComparer ?? EqualityComparer<TKey>.Default;
_valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
}
/// <summary>
/// Equality.
/// </summary>
public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y) => _keyComparer.Equals(x.Key, y.Key) && _valueComparer.Equals(x.Value, y.Value);
/// <summary>
/// Hash code.
/// </summary>
public int GetHashCode(KeyValuePair<TKey, TValue> obj) => new HashCode() + _keyComparer.GetHashCode(obj.Key) + _valueComparer.GetHashCode(obj.Value);
}
/// <summary>
/// <see cref="IEqualityComparer{T}"/> for a collection.
/// </summary>
public sealed class CollectionComparer<TElement> : IEqualityComparer<ICollection<TElement>>
{
/// <summary>
/// Gets an instance using <see cref="EqualityComparer{T}.Default"/> as the element comparer.
/// </summary>
public static CollectionComparer<TElement> Default { get; } = new CollectionComparer<TElement>();
private readonly IEqualityComparer<TElement> _elementComparer;
/// <summary>
/// Initialize with a specific element comparer.
/// </summary>
public CollectionComparer(IEqualityComparer<TElement> elementComparer = null)
{
_elementComparer = elementComparer ?? EqualityComparer<TElement>.Default;
}
/// <summary>
/// Determines whether the specified objects are equal.
/// </summary>
/// <returns>
/// true if the specified objects are equal; otherwise, false.
/// </returns>
public bool Equals(ICollection<TElement> x, ICollection<TElement> y)
{
if (x == null) return y == null;
if (y == null) return false;
return x.Count == y.Count && x.SequenceEqual(y, _elementComparer);
}
/// <summary>
/// Returns a hash code for the specified object.
/// </summary>
/// <returns>
/// A hash code for the specified object.
/// </returns>
/// <param name="obj">The <see cref="T:System.Object"/> for which a hash code is to be returned.</param>
public int GetHashCode(ICollection<TElement> obj)
{
var result = new HashCode() + typeof(TElement).GetHashCode();
if (obj == null) return result;
result += obj.Count;
result += obj.Select(element => _elementComparer.GetHashCode(element));
return result;
}
}
}