2

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>).

evilkos
  • 545
  • 10
  • 17
  • 1
    Why don't you make the method generic (`IEnumerable GetHashSet(IEnumerable source)`)? If you don't, you have to resort to this "ugly" approach. Also, is your actual question _"How can I make this code less ugly"_? You can of course skip the list altogether and add the items from `source` to the HashSet directly, by finding the appropriate `Add` method. – CodeCaster Jan 18 '16 at 15:36
  • Thanks. I don't know T at compilation time, so I can't use a method with this signature, nor can I use the `Add` method (because, of what type). – evilkos Jan 18 '16 at 15:40
  • It looks, that you try to avoid duplicates (cause you return the HashSet as IEnumerable). If that's the case, maybe take a look at [this answer](http://stackoverflow.com/questions/489258/linqs-distinct-on-a-particular-property) or [this answer](http://stackoverflow.com/questions/998066/linq-distinct-values). – Oliver Jan 18 '16 at 15:40
  • I suggest you create an unsafe wrapper for `ICollection` which implements `ICollection` and casts. Create an instance of this using reflection along with the inner set and then adds each object through the `ICollection.Add` method. – Lee Jan 18 '16 at 15:41
  • @Oliver, thanks, no, that's not the case. HashSet is used because it seems to be the preference of EntityFramework 6 for collections on entities. And the code is for a class of very general nature. CrudController that tries to convert string ids from any MultiSelectLists on a page to updated relationships on an EF entity, whatever type it might be. – evilkos Jan 18 '16 at 15:43
  • @Lee, can you clarify, how will I get a HashSet in the end using this approach? – evilkos Jan 18 '16 at 15:45
  • @evilkos - The `HashSet` will be referenced by the wrapper so just add a property to return it, or have the wrapper implement `IEnumerable` directly. – Lee Jan 18 '16 at 15:48
  • @Lee, oh, I think I got it, this should work. But it will be too much code if I'm not mistaken. – evilkos Jan 18 '16 at 15:58

2 Answers2

11

This should do it:

public IEnumerable<T> GetHashSet<T>(IEnumerable<T> source)
{
    return new HashSet<T>(source);
}
NikolaiDante
  • 18,469
  • 14
  • 77
  • 117
  • Thanks. I don't know T at compilation time, so I can't use a method with this signature. It is for a class of very general nature. CrudController that tries to convert string ids from any MultiSelectLists to updated relationships on an EF entity. – evilkos Jan 18 '16 at 15:39
  • 2
    @evilkos If the source had been strongly typed, `GetHashSet(source)` would have worked. The type parameter `T` would have been inferred. You should be able to use the method with a cast to **`dynamic`** if you do not have the strong type at compile-time. That would be `var result = (IEnumerable)GetHashSet((dynamic)source);`. – Jeppe Stig Nielsen Jan 18 '16 at 21:40
  • @JeppeStigNielsen, wow, you've just tought me a new thing in c#! I never thought `dynamic` had a use, like, anywhere. But I can actually workaround strong-typing issues like this with it. Looks like a hack though, but maybe not much worse than using reflection. Sadly, a little later it came to my attention that my `IEnumerable source` is actually always `IEnumerable` inside, so this trick won't work with it. – evilkos Jan 19 '16 at 09:40
5

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;
}
thehennyy
  • 4,020
  • 1
  • 22
  • 31
  • This will fail at runtime if `source` does not implement `IEnumerable`. – Lee Jan 18 '16 at 15:52
  • @Lee, but it does. It is stated in the beginning of my question, albeit not very clear: "`IEnumerable llist` with value of IEnumerable)" – evilkos Jan 18 '16 at 15:54
  • @thehennyy, yep, that is something that I've been asking for. At least it looks 2 times smaller. Thank you! – evilkos Jan 18 '16 at 15:56
  • If this is being used in something that's not a private method, then I suggest doing a little bit more checking at runtime, then throwing an `ArgumentException` at runtime if eg. the IEnumerable doesn't implement IEnumerable, etc. Also, I'd like to note that this isn't a particularly cheap way to produce objects, because it uses reflection. – willaien Jan 18 '16 at 16:00
  • @willaien, thanks for the tips, they are all valid. This method will be private and its only parameter is also created by me, so nothing to worry about here, I think. Speed is also my concern, but I think it'll do for my MVC5 controller action (html). This method should not be called more frequently than a hundred times a day :) it's not a public app – evilkos Jan 18 '16 at 16:07
  • @evilkos: Well, the performance hit is probably measured in milliseconds or less, so, no big deal. If this were being hit millions of times a second, then it would be worth worrying about. – willaien Jan 18 '16 at 16:26