Reputation: 112
Aim: Using reflection and generics pass in a VBA array of custom objects and return the same custom VBA COM object back after processing. Eg in the example removing duplicates from a VBA of any type array passed in as a variant containing an array.
For value types worked as expected returning an array of the same type passed in. See RemoveDuplicates
Issue: For custom VBA objects eg a Person array returns an array of variants containing person objects where expecting a Person array.
To remove duplicates for a custom object arrays RemoveDuplicates2 is used passing in the VBA array and an non-generic IEqualityComparer.
Apart from not returning the custom COM object array required is working as expected removing the duplicates within a Person array according to the IEqualityComparer supplied from VBA.
Note: For now named the RemoveDuplicates overloads in Array.cs uniquely to avoid any potiential issues requiring the knowlegde of expression trees to be investigated later.
SafeArraySingleton.cs
using GSystem = global::System;
using GArray = global::System.Array;
using GType = global::System.Type;
using GMethodInfo = global::System.Reflection.MethodInfo;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Collections;
using DotNetLib.System.Collections.Generic;
namespace DotNetLib.System
{
[ComVisible(true)]
[Description("Provides methods for creating, manipulating, searching, and sorting safe arrays.")]
[Guid("37592E87-83CE-4D47-A510-324FE529BE27")]
[ProgId("DotNetLib.System.SafeArraySingleton")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(ISafeArraySingleton))]
public class SafeArraySingleton : ISafeArraySingleton
{
const string RemoveDuplicatesMethod = "RemoveDuplicates";
static GType _arrayType = typeof(Array);
static GMethodInfo _removeDuplicates = _arrayType.GetMethod("RemoveDuplicates",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
static GMethodInfo _removeDuplicates2 = _arrayType.GetMethod("RemoveDuplicates2",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
static GType _genericComparerType = typeof(GenericEqualityComparer<>);
public SafeArraySingleton() { }
[return: MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)]
public object RemoveDuplicates([MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)][In] ref object inputArray)
{
if ((inputArray == null) | !(inputArray is GArray))
{
throw new ArgumentException("Must be an array.", nameof(inputArray));
}
var result = _removeDuplicates.MakeGenericMethod(inputArray.GetType().GetElementType()).Invoke(null, new object[] { inputArray });
return result;
}
[return: MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)]
public object RemoveDuplicates2([MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)][In] ref object inputArray, IEqualityComparer nonGenericComparer)
{
if ((inputArray == null) | !(inputArray is GSystem.Array))
{
throw new ArgumentException("Must be an array.", nameof(inputArray));
}
GType elementType = inputArray.GetType().GetElementType();
// Create a generic wrapper for the non-generic comparer using reflection
var genericComparerType = typeof(GenericEqualityComparer<>);
var genericType = genericComparerType.MakeGenericType(elementType);
var genericComparer = Activator.CreateInstance(genericType, nonGenericComparer);
var result = _removeDuplicates2.MakeGenericMethod(elementType).Invoke(null, new object[] { inputArray, genericComparer });
return result;
}
}
}
ISafeArraySingleton.cs
using GCollections = global::System.Collections;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace DotNetLib.System
{
[ComVisible(true)]
[Guid("5B473306-F5E2-4C83-92F7-CEF4A363E2DD")]
[Description("Provides methods for creating, manipulating, searching, and sorting safe arrays.")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ISafeArraySingleton
{
[Description("Removes duplicates from an array.")]
[return: MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)]
object RemoveDuplicates([MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)][In] ref object inputArray);
[Description("Removes duplicates from an array.")]
[return: MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)]
object RemoveDuplicates2([MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)][In] ref object inputArray, GCollections.IEqualityComparer equalityComparer);
}
}
From Array.cs
public static T[] RemoveDuplicates<T>(T[] inputArray)
{
if (inputArray == null)
throw new ArgumentNullException(nameof(inputArray));
HashSet<T> set = new HashSet<T>(inputArray);
return set.ToArray();
}
public static T[] RemoveDuplicates2<T>(T[] inputArray, IEqualityComparer<T> equalityComparer)
{
if (inputArray == null)
throw new ArgumentNullException(nameof(inputArray));
HashSet<T> set = new HashSet<T>(inputArray, equalityComparer);
return set.ToArray();
}
GenericEqualityComparer.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace DotNetLib.System.Collections.Generic
{
[ComVisible(false)]
// Generic wrapper for non-generic IEqualityComparer
// GenericEqualityComparer<T> is a generic wrapper that delegates to the non-generic comparer.
public class GenericEqualityComparer<T> : IEqualityComparer<T>
{
private readonly IEqualityComparer originalComparer;
public GenericEqualityComparer(IEqualityComparer originalComparer)
{
this.originalComparer = originalComparer ?? throw new ArgumentNullException(nameof(originalComparer));
}
public bool Equals(T x, T y)
{
return originalComparer.Equals(x, y);
}
public int GetHashCode(T obj)
{
return originalComparer.GetHashCode(obj);
}
}
}
Upvotes: 0
Views: 68