Search code examples
c#.netimmutability.net-coreimmutable-collections

Creating an ImmutableList of a type that is unknown at compile-time


Given a Collection<T> whose type T is only known in runtime (not at compile time), I would like to generate an ImmutableList<T>.

The method I would like to create may like like:

var immutableList = CreateImmutableList(originalList, type);

where originalList is IEnumerable and type is the T of of the generated ImmutableList<T>.

How?!

(I'm working with NET .Core)

EDIT: Thanks to the comments I have found a working solution. It uses AddRange method.

namespace Sample.Tests
{
    using System;
    using System.Collections;
    using System.Collections.Immutable;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Reflection;
    using Xunit;

    public class ImmutabilityTests
    {
        [Fact]
        public void CollectionCanBeConvertedToImmutable()
        {
            var original = new Collection<object>() { 1, 2, 3, 4, };
            var result = original.AsImmutable(typeof(int));

            Assert.NotEmpty(result);
            Assert.IsAssignableFrom<ImmutableList<int>>(result);
        }
    }

    public static class ReflectionExtensions
    {
        public static IEnumerable AsImmutable(this IEnumerable collection, Type elementType)
        {
            var immutableType = typeof(ImmutableList<>).MakeGenericType(elementType);
            var addRangeMethod = immutableType.GetMethod("AddRange");
            var typedCollection = ToTyped(collection, elementType);

            var emptyImmutableList = immutableType.GetField("Empty").GetValue(null);
            emptyImmutableList = addRangeMethod.Invoke(emptyImmutableList, new[] { typedCollection });
            return (IEnumerable)emptyImmutableList;
        }

        private static object ToTyped(IEnumerable original, Type type)
        {
            var method = typeof(Enumerable).GetMethod("Cast", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(type);
            return method.Invoke(original, new object[] { original });
        }
    }
}

Solution

  • You can do this using reflection:

    1. Create the right Type object for ImmutableList<T>
    2. Fill it with data
    3. Return it

    Here is a LINQPad program that demonstrates. I'm assuming that by "immutable list" you mean System.Collections.Immutable.ImmutableList<T> available through Nuget:

    void Main()
    {
        object il = CreateImmutableList(new[] { 1, 2, 3, 4, 5 }, typeof(int));
        il.GetType().Dump();
        il.Dump();
    }
    
    public static object CreateImmutableList(IEnumerable collection, Type elementType)
    {
        // TODO: guard clauses for parameters == null
        var resultType = typeof(ImmutableList<>).MakeGenericType(elementType);
        var result = resultType.GetField("Empty").GetValue(null);
        var add = resultType.GetMethod("Add");
        foreach (var element in collection)
            result = add.Invoke(result, new object[] { element });
        return result;
    }
    

    Output:

    System.Collections.Immutable.ImmutableList`1[System.Int32]
    
    1
    2
    3
    4
    5