evilkos
evilkos

Reputation: 597

How to elegantly convert IEnumerable<T> to HashSet<T> at runtime without knowing T beforehand

In short, I need to convert IEnumerable list (with value of IEnumerable<T>) to HashSet<T> set without knowing T at compilation time. The only way I figured it can be done is as following, but I find it extremely ugly.

public IEnumerable GetHashSet(IEnumerable source)
{
    Type itemType = source.GetType().GetGenericArguments()[0];
    Type listOpen = typeof(List<>);
    Type listClosed = listOpen.MakeGenericType(new Type[] { itemType });
    IList list = Activator.CreateInstance(listClosed) as IList;
    foreach (var obj in source)
        list.Add(obj);
    Type hashSetOpen = typeof(HashSet<>);
    Type hashSetClosed = hashSetOpen.MakeGenericType(new Type[] { itemType });
    return Activator.CreateInstance(hashSetClosed, list) as IEnumerable;
}

The problem is, HashSet<T> does not have any way of adding an object via some non-generic interface (in contrast, List<T> has IList.Add(object)). Also it does not have a constructor that takes a "bare" IEnumerable (neither does List<T>).

Upvotes: 2

Views: 6893

Answers (2)

thehennyy
thehennyy

Reputation: 4216

Original answer: You can do it this way if you want to insist on your method signature:

private static IEnumerable GetHashSet(IEnumerable source)
{
    var type = source.GetType().GetGenericArguments()[0];
    var ctor = typeof(HashSet<>).MakeGenericType(type)
                .GetConstructor(new[] {typeof (IEnumerable<>).MakeGenericType(type)});
    return ctor.Invoke(new object[] { source }) as IEnumerable;
}

Improved: As mentioned in the comments, often it is better to be more explicit about what a function is expected to do, so i added the necessary checks:

private static IEnumerable GetHashSet(IEnumerable source)
{
    var inputType = source.GetType();
    if (!inputType.IsGenericType || inputType.IsGenericTypeDefinition)
        throw new ArgumentException(nameof(source));

    var genericArgumentType = inputType.GetGenericArguments()[0];
    var iEnumerableType = typeof (IEnumerable<>).MakeGenericType(genericArgumentType);

    if (!iEnumerableType.IsAssignableFrom(inputType))
        throw new ArgumentException(nameof(source));

    var ctor = typeof (HashSet<>).MakeGenericType(genericArgumentType)
        .GetConstructor(new[] {iEnumerableType});

    if (ctor == null)
        throw new Exception("ctor not found.");

    return ctor.Invoke(new object[] { source }) as IEnumerable;
}

Upvotes: 5

NikolaiDante
NikolaiDante

Reputation: 18649

This should do it:

public IEnumerable<T> GetHashSet<T>(IEnumerable<T> source)
{
    return new HashSet<T>(source);
}

Upvotes: 11

Related Questions