使用带有反射

时间:2015-07-28 13:29:29

标签: c# .net reflection

我有这个帮助方法,用于将集合项从一个集合对象实例传输到另一个集合对象实例。它有效,但我最近遇到了一个特定集合在不同点IEnumerable<T>;实现的问题。在IEnumerable<KeyValuePair<TKey, TValue>>和另一个IEnumerable<TValue>的一个级别。在我的下面的代码中,secondaryCollection的声明使它使用IEnumerable<TValue>实例类型而不是collectionType声明将其作为基本ICollection<KeyValuePair<TKey, TValue>>类型,以便我可以调用Add() }和Remove()。虽然Add()Remove()方法调用失败,但此类型不匹配。我想如果我能弄清楚如何将secondaryCollection声明为IEnumerable<object>类型,其中'object'的类型为KeyValuePair<TKey, TValue>,而不仅仅是类型TValue类型不匹配异常(它实际上是Add()Remove()方法的参数异常)。问题是这都是在反思中完成的,类型是未知的。我怎么能这样做?

这是当前的方法代码:

public void MergeCollection(FieldInfo primaryMember, object primaryObject, FieldInfo secondaryMember, object secondaryObject)
    {
        if (primaryMember == null)
            throw new ArgumentNullException("primaryMember");

        if (primaryObject == null)
            throw new ArgumentNullException("primaryObject");

        if (secondaryMember == null)
            throw new ArgumentNullException("secondaryMember");

        if (secondaryObject == null)
            throw new ArgumentNullException("secondaryObject");

        //Get the collection type and validate
        Type genericType = typeof(ICollection<>);

        Type collectionType = primaryMember.FieldType.GetBaseTypes().FirstOrDefault(t => t.IsGenericType && t.GetGenericArguments().Length == 1 && t == genericType.MakeGenericType(t.GetGenericArguments()));

        if (!collectionType.IsAssignableFrom(secondaryMember.FieldType))
            throw new InvalidOperationException("Primary and secondary collection types do not match.");

        Type collectionParamType = collectionType.GetGenericArguments()[0];


        //Get the collection invocable methods
        MethodInfo add = collectionType.GetMethod("Add", new Type[] { collectionParamType });
        MethodInfo remove = collectionType.GetMethod("Remove", new Type[] { collectionParamType });

        //Declare the collections
        object primaryCollectionObject = primaryMember.GetValue(primaryObject);
        object secondaryCollectionObject = secondaryMember.GetValue(secondaryObject);

        Type genericEnumerableType = typeof(IEnumerable<>);
        Type enumerableType = primaryMember.FieldType.GetBaseTypes().FirstOrDefault(t => t.IsGenericType && t.GetGenericArguments().Length == 1 && t == genericEnumerableType.MakeGenericType(t.GetGenericArguments()));

        IEnumerable<object> secondaryCollection = ((IEnumerable)secondaryCollectionObject).Cast<object>();

        //Transfer the items
        int noItems = secondaryCollection.Count();
        // int noItems = (int)count.GetValue(secondaryCollectionObject);
        for (int i = 0; i < noItems; i++)
        {
            try
            {
                add.Invoke(primaryCollectionObject, new object[] { secondaryCollection.ElementAt(0) });
                remove.Invoke(secondaryCollectionObject, new object[] { secondaryCollection.ElementAt(0) });
            }
            catch (ArgumentException ex)
            {
                //The argument exception can be captured here
            }
        }
    }

修改

也许只是为我需要帮助的内容添加一些说明...我有一个自定义集合,用于通过使用反射的方法评估的类。此集合实现IEnumerable两次...... IEnumerable<TValue>IEnumerable<KeyValuePair<TKey, TValue>>。而不是......

IEnumerable<object> secondaryCollection = ((IEnumerable)secondaryCollectionObject).Cast<object>();

由于IEnumerable<TValue>操作而最终使用Cast<T>(),我需要secondaryCollection使用IEnumerable<KeyValuePair<TKey, TValue>>的内容。并且它无法知道该集合最初使用了两个实现。从这一行开始:

Type collectionType = primaryMember.FieldType.GetBaseTypes().FirstOrDefault(t => t.IsGenericType && t.GetGenericArguments().Length == 1 && t == genericType.MakeGenericType(t.GetGenericArguments()));

确定了正确的类型,我原本以为它可以使用,但我不确定如何。

3 个答案:

答案 0 :(得分:1)

刚刚看到Zotta在我准备答案时带来了类似的想法。无论如何,这是我的。最简单的方法是将主要实现放在“纯”C#泛型方法中,然后从反射中调用它。现在的问题是如何通过反射调用泛型方法。有几个SO项目,到目前为止最简单的方法是使用动态功能。这是一个示例解决方案,包括动态或纯反射方法(跳过验证)和简单测试:

static class Helper
{
    public static void Merge<T>(ICollection<T> source, ICollection<T> target)
    {
        foreach (var item in source) target.Add(item);
        source.Clear();
    }

    #region Using dynamic

    public static void MergeCollection(FieldInfo sourceMember, object sourceObject, FieldInfo targetMember, object targetObject)
    {
        var sourceCollection = sourceMember.GetValue(sourceObject);
        var targetCollection = targetMember.GetValue(targetObject);
        Merge((dynamic)sourceCollection, (dynamic)targetCollection);
    }

    #endregion

    #region Using reflection only

    public static void MergeCollection2(FieldInfo sourceMember, object sourceObject, FieldInfo targetMember, object targetObject)
    {
        var collectionType = targetMember.FieldType.GetInterfaces().Single(
            t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ICollection<>)
        );
        var itemType = collectionType.GetGenericArguments()[0];
        var mergeMethod = MergeMethodInfo.MakeGenericMethod(itemType);
        var sourceCollection = sourceMember.GetValue(sourceObject);
        var targetCollection = targetMember.GetValue(targetObject);
        mergeMethod.Invoke(null, new[] { sourceCollection, targetCollection });
    }

    private static readonly MethodInfo MergeMethodInfo = GetGenericMethodDefinition(
        (ICollection<object> source, ICollection<object> target) => Merge(source, target)
    );

    private static MethodInfo GetGenericMethodDefinition<T1, T2>(Expression<Action<T1, T2>> e)
    {
        return ((MethodCallExpression)e.Body).Method.GetGenericMethodDefinition();
    }

    #endregion

    #region Test

    class MyCollection1<TKey, TValue> : Dictionary<TKey, TValue>, IEnumerable<TValue>
    {
        IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() { return Values.GetEnumerator(); }
    }

    class MyCollection2<TKey, TValue> : List<KeyValuePair<TKey, TValue>>, IEnumerable<TValue>
    {
        IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
        {
            IEnumerable<KeyValuePair<TKey, TValue>> e = this;
            return e.Select(item => item.Value).GetEnumerator();
        }
    }

    class MyClass1
    {
        public MyCollection1<int, string> Items1 = new MyCollection1<int, string>();
    }

    class MyClass2
    {
        public MyCollection2<int, string> Items2 = new MyCollection2<int, string>();
    }

    private static FieldInfo GetField<T, V>(Expression<Func<T, V>> e)
    {
        return (FieldInfo)((MemberExpression)e.Body).Member;
    }

    public static void Test()
    {
        var source = new MyClass1();
        for (int i = 0; i < 10; i++) source.Items1.Add(i + 1, new string((char)('A' + i), 1));
        var target = new MyClass2();
        var sourceField = GetField((MyClass1 c) => c.Items1);
        var targetField = GetField((MyClass2 c) => c.Items2);
        // Merge source into target using dynamic approach
        MergeCollection(sourceField, source, targetField, target);
        // Merge target back into source using reflection approach
        MergeCollection2(targetField, target, sourceField, source);
    }

    #endregion
}

答案 1 :(得分:0)

正如您所说的那样,问题在于Cast方法。因为你没有在Cast方法中传递正确的类型,所以IEnumrable会出错。解决方案不是使用类型&#34; object&#34;来调用Cast方法,而是使用正确的类型。由于您在编译时没有正确的类型信息,您可能需要使用反射调用Cast,如下所示:

var castMethod = typeof(Enumerable).GetMethod("Cast", BindingFlags.Static | BindingFlags.Public);
var castGenericMethod = castMethod.MakeGenericMethod(new Type[] {  collectionParamType});
secondaryCollection = castGenericMethod.Invoke(null, new object[] {secondaryCollectionObject})

注意:我刚刚在iPad上输入了上述代码,因此可能存在一些语法问题。

答案 2 :(得分:0)

这可能有用(只需将集合作为源和目标传递):

public static void MoveItems(dynamic source, dynamic target) {
    MoveItemsImpl(source, target);
}

public static void MoveItemsImpl<T>(ICollection<T> source, dynamic target) {
    foreach(T item in source)
        target.Add(item);
    source.Clear();//optional
}

有点hacky,但值得一试:)