2015-08-10 12 views
6

Próbuję użyć drzewek wyrażeń LINQ do zainicjowania nowego anonimowego obiektu z podzestawem pól z obiektu przekazanego. Podzbiór pól będzie zazwyczaj pojedynczą właściwością, ale może być większy (4 lub więcej).Drzewo wyrażeń do zainicjowania nowego anonimowego obiektu z dowolną liczbą właściwości

Przykład:

void DoWork(Type t) 
{ 
    //do some work to figure out which properties I want to use 
    List<PropertyInfo>() props = (from p in t.GetProperties() 
            where p.HasAttribute(MyAttribute) 
            select p).ToList(); 

    //now I want to create a lambda expression that's like: 
    // (x=> new {x.Prop1, x.Prop2, ...}) 
    //for each property in the list props 

    //create parameter 
    var param = Expression.Parameter(t,"t"); 
    //get list of expressions for properties 
    var initValues = (from p in props 
         select Expression.PropertyOrField(param, p.Name) 
         ).ToList(); 

    //build expression body 
    Expression body = Expression. //<<<< I have no idea what to do here 

    //create lambda 
    dynamic propLambda = Expression.Lambda(body, param); 

    //use it! MyFunction(x=> new {x.Prop1, x.Prop2,...}); 
    MyFunction(lambda); 
} 
+1

Inicjowanie anonimowego obiektu odbywa się za pomocą konstruktora typu. –

+3

W związku z tym pytanie zwrotne może brzmieć: Jak utworzyć nowy typ w oparciu o kolekcję właściwości wyodrębnionych z zapytania o odbicie? – JuanK

+0

jaki jest dokładnie twój problem? –

Odpowiedz

3

Zła wiadomość: Ponieważ masz zamiar budować ten typ anonimowy dynamicznie w czasie wykonywania, to nie istnieje w czasie kompilacji, gdyż jeśli kompilator napotkał new {...} w Twój kod. Nie ma sposobu obejścia Reflection.Emit, który nie jest równy.

Dobra wiadomość: już to zrobiłem, ponieważ miałem te same potrzeby. Użył ILDASM, aby zobaczyć, co kompilator generuje i naśladować, jak najbliżej.

Więc bez zbędnych ceregieli:

/// <summary> 
/// Creates types that are much like anonymous types. 
/// </summary> 
public static class TupleFactory 
{ 
    // the dynamic module used to emit new types 
    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 
     { 
      ITupleFactory result; 
      if (_newExpressionFactories.TryGetValue(propList, out result)) // we already have it 
       return result; 

      var propertyNames = propList.Select(p => p.Key).ToList(); 
      Type genericTypeDefinition; 
      if (!_genericTypeDefinitions.TryGetValue(propertyNames, out 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; 
    } 
} 

Zastosowanie:

void DoWork(Type type) 
{ 
    var props = type.GetProperties().Where(p => p.HasAttribute(MyAttribute)).ToList(); 
    var tupleFactory = TupleFactory.Create(props.Select(p => new KeyValuePair<string, Type>(p.Name, p.PropertyType))); 
    var param = Expression.Parameter(type, "x"); 
    var newEx = tupleFactory.MakeNewExpression(props.Select(p => Expression.Property(param, p))); 
    var lambda = Expression.Lambda(newEx, param); // <-- type is LambdaExpression, not dynamic 
    MyFunction(lambda); 
} 
Powiązane problemy