Search code examples
genericsscriptingcom-interop

Why is it that ArrayList works with COM Interop, but IList<T> doesn't?


I've noticed that if I make a .NET component that exposes an ArrayList, then that ArrayList passes through COM Interop and is available in scripted languages such as VBScript.

Generics, such as IList<T> do not seem to work.

Why is this and is there any way to make a generic type successfully flow through COM Interop to a scripting engine?


Solution

  • Generics were added in .NET 2.0 and COM existed before .NET 1.0.
    (And was the technology .NET aimed to replace.)

    COM never had generics, and therefore you cannot expose them.
    Neither of COM languages (C++, VB6, Delphi) had generics so you can't expect them to be consumed.
    (Well, C++ had templates but they are completely a different beast, and COM only meant interfaces.)

    Exposing collections as an ArrayList is the solution to this problem, you can't work around it.

    Disclaimer: I am no expert on COM so the rest of the answer is roughly based on my guesses.

    COM never “had” ArrayLists, true, but so it never had any of the classes in .NET Framework, because it is not a framework itself. However, some of the .NET types get into the exported type libraries, and some don't. What about .NET Framework classes? Well, ArrayList is [ComVisible], and List<T> isn't.

    Why?

    COM works via interfaces, and the Interface Definition Language has no clue about generics and doesn't support them. Languages that support COM, such as VB6 or C++, wouldn't know what to do with the generics.

    If there was a way to generate an interface for List<T>, it would not contain T in it so there is essentially no point in trying to expose a generic type. Possible imaginary alternatives to this would be:

    • Generating a “specific” version of interface for a generic type, e.g. IListOfString for List<string>
    • Erasing generic type information (much like Java does on compilation) and replacing T with object.

    The first option is not viable because the concrete T type may not be known at compilation (read: Reflection), and List<T> doesn't have a [ComVisible] attribute on it anyway.

    The second option is actually sort of possible because you can provide your own class interface with IList and ICollection properties:

    [ComVisible(true)]
    public interface IPerson
    {
        string Name { get;set;}
        DateTime Entered { get;set;}
        IList NickNamesList { get;}
        ICollection NickNamesCollection { get;}
    }
    
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComDefaultInterface(typeof(IPerson))]
    public class Person:IPerson
    {
        [ComVisible(false)]
        public List<string> NickNames
        {
            get { return _NickNames; }
            set { _NickNames = value; }
        }
        private List<string> _NickNames = new List<string>();
    
        #region IPerson Members
        IList IPerson.NickNamesList
        {
            get { return this.NickNames; }
        }
    
        ICollection IPerson.NickNamesCollection
        {
            get { return this.NickNames; }
        }
        #endregion
        ....
    }
    

    This is a workaround and doesn't answer your question though.

    I am actually wondering if you could derive your StringList class from List<string> and mark it as [ComVisible(true)]. You may want to check that.