我有这个帮助方法,用于将集合项从一个集合对象实例传输到另一个集合对象实例。它有效,但我最近遇到了一个特定集合在不同点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()));
确定了正确的类型,我原本以为它可以使用,但我不确定如何。
答案 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,但值得一试:)