Skip to content
Snippets Groups Projects
Select Git revision
  • 148d327cff9726e425dc0cc546e320937dfe8e59
  • master default
  • JS-based-scroll-rendering
  • Paul_Marius_Level
  • Paul_Marius_2
  • Paul_Marius
  • Andi_Mark
  • be-UnityWebView
  • gitignoreFrameitServer
  • ZimmerBSc
  • Bugfix_StageLoading
  • stages
  • MAZIFAU_Experimental
  • tsc/coneworld
  • tsc/fact-interaction
  • marcel
  • MaZiFAU_TopSort
  • mergeHelper
  • zwischenSpeichern
  • tempAndrToMaster
  • SebBranch
  • 3.0
  • v2.1
  • v2.0
  • v1.0
25 results

TupleFactory.cs

Blame
  • TupleFactory.cs 22.11 KiB
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Reflection.Emit;
    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, via string-Type-pairs.
        /// </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.
            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;
            }
        }
    }