Search code examples
c#listsetcontravariance

C# Non-Generic ISet Interface


.NET 4.0 introduced a non-generic IList which exposes the ability to add values to the List without needing to know the generic type. This is useful because it allows me to write a method such as the following:

void CreateListFromBytes(IntPtr bytes, Type outputType, out object outputObject)
{
    Type elementType = outputType.GenericTypeArguments[0];
    int numberOfElements = ReadHeaderBytes(bytes);
    bytes += Marshal.SizeOf(typeof(int));

    IList outputList = (IList) Activator.CreateInstance(outputType);
    for (int i = 0; i < numberOfElements; i++)
    {
        object element = ReadDataBytes(bytes, elementType);
        bytes += Marshal.SizeOf(elementType);
        outputList.Add(element);
    }

    outputObject = outputList;
}

However, when I try to implement a method with a similar style for HashSet or ISet, there is not such non-generic interface I can find that exposes and Add() method.

I am wondering if such an interface exists that I may have missed. If not, I am wondering how I can go about adding elements to object I know for certain is Set (since I created it the Activator.CreateInstance())


Solution

  • I would end up with a couple of aux types for constructing a set:

    interface ISetBuilder 
    {
        void Add(object item);
        object Build();
    }
    
    class SetBuilder<T, TSet> : ISetBuilder where TSet : ISet<T>, new() 
    {
        private readonly TSet _set = new TSet();
    
        public void Add(object item) 
        {
            if (!(item is T typedItem)) 
            {
                throw new ArgumentException();
            }
    
            _set.Add(typedItem);
        }
    
        public object Build() => _set;
    }
    

    Those types then could be used like this:

    var builderType = typeof(SetBuilder<,>).MakeGenericType(elementType, outputType);
    var builder = (ISetBuilder) Activator.CreateInstance(builderType);
    var element = CreateElement(...);
    builder.Add(element);
    var set = builder.Build();
    

    And yes, this could be generalised to support lists as well. Just replace ISet<T> with ICollection<T>.

    Another possible (but a bit less robust) solution is just to find and call the specific Add method of a set by using reflection.