2

Can I use a collection initializer on my class which have an Add method that takes a generic parameter?

My class looks like this:

public class FooCollection : IEnumerable<KeyValuePair<string, Type>>
{
    private readonly IDictionary<string, Type> _directory = new Dictionary<string, Type>();

    public void Add<T>(string name)
    {
        _directory.Add(name, typeof(T));
    }

    public IEnumerator<KeyValuePair<string, Type>> GetEnumerator()
    {
        return _directory.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _directory.GetEnumerator();
    }
}

I can do:

var collection = new FooCollection();
collection.Add<Foo>("Foo");
collection.Add<Bar>("Bar");
collection.Add<Baz>("Baz");

But I would like to use a collection initializer. Can I do that? How?

Fred
  • 12,086
  • 7
  • 60
  • 83
  • This doesn't really make sense. If you're naming it the same as the class, when would you ever have the same class with a different class name? Are you wanting to instantiate the entire dictionary at once? Then why do you have an add method? Just make your Directory dictionary a public property, then do a collection initializer like you would anywhere else. – Dispersia Oct 11 '16 at 17:54
  • Because a dictionary can only accept instances of objects. You cant insert a type into a dictionary, without doing `.Add(typeof(Foo), "Foo")` and having to use `typeof` is ugly, also then you cant constrain the type to a certain type, such as `Foo` or any class that derives from `Foo`. – Fred Oct 11 '16 at 18:17

2 Answers2

2

You can try this:

    public static class StringToTypeMapExtensions
    {
        public static void Add<T>(this StringToTypeMap map, T prototype)
            where T : SomeBase
        {
            map.Add(typeof(T).Name, typeof(T));
        }
    }

    public class StringToTypeMap : Dictionary<string, Type>
    {
    }

    public class SomeBase { }
    public class Foo : SomeBase { }
    public class Bar : SomeBase { }
    public class Baz : Bar { }
    public class Alien { }

Which will allow you to write:

    class Program
    {
        static void Main(string[] args)
        {
            var map =
                new StringToTypeMap
                {
                    default(Foo),
                    default(Bar),
                    default(Baz)
                };
            foreach (var key in map.Keys)
            {
                Console.WriteLine("{0} : {1}", key, (object)map[key] ?? "(null)");
            }
            Console.ReadKey();
        }
    }

(Thus, avoiding to force you into implementing ICollection < Type > just for the sake of its Add(Type value) method)

Or this, also, will compile and run as expected:

            var map =
                new StringToTypeMap
                {
                    { string.Empty, null },
                    { "Integer", typeof(int) },
                    { nameof(Decimal), typeof(decimal) },

                    default(Foo),
                    default(Bar),
                    default(Baz)
                };
            foreach (var key in map.Keys)
            {
                // etc
            }

But this won't compile (because of Alien):

            var map =
                new StringToTypeMap
                {
                    default(Foo),
                    default(Bar),
                    default(Baz),
                    default(Alien)
                };

(This works because collection initializers also honor "Add" extension methods, when those are in scope)

See: https://msdn.microsoft.com/en-us/library/bb384062.aspx

(quoting)

"Collection initializers let you specify one or more element initializers when you initialize a collection class that implements IEnumerable or a class with an Add extension method [...]"

'Hope this helps.

YSharp
  • 1,066
  • 9
  • 8
  • The key here is to provide a type parameter from which the generic type can be inferred. There's no need to put it in an extension method; it works fine using the same approach in the `FooCollection()` class as originally posted in the question. (Though if the only reason to subclass the type is to write the `Add()` method, then yet, maybe the extension method is nicer...but it's not _necessary_, just to provide an answer to the question and does complicate the answer). – Peter Duniho Oct 11 '16 at 20:06
  • Well, I hear you. But the OP's question is a bit unclear so I only used an intelligent guess; so maybe I should have remarked upfront: calling a "collection" something that isn't one (as it's more like a map, actually) is confusing. So, I assumed the OP's focus was on making profit of the collection initializer syntactic sugar and type inference if only for an initialization code leaner to write and read, while allowing also for generic constraints, independently of what the class functionality may be otherwise, and yet not having to require any use of generics on the class itself. – YSharp Oct 11 '16 at 20:21
  • _"calling a "collection" something that isn't one"_ -- I don't know what this has to do with the answer you wrote (i.e. choosing to forego the original code in lieu of your own new example), but it seems to me you're splitting hairs far too finely. `Dictionary` is declared in the `System.` **`Collections`** `.Generic` namespace, benefits from the C# **collection** initializer syntax, and even implements `ICollection`. It sure seems like _some_ kind of a collection to me, and certainly not different enough from one to warrant changing the question itself for your answer. – Peter Duniho Oct 11 '16 at 20:26
  • Not splitting hair. I was referring to the OP: "public class FooCollection : IEnumerable>" -- so, yes, I am aware that a dictionary *is* a collection of key value pairs; hence why I proposed to base the implementation on Dictionary in my answer, in a one liner, to stress out that there's no need for all the boilerplate of reimplementing ICollection and the likes, if the main point of interest is just to leverage collection initializers. – YSharp Oct 11 '16 at 20:36
0

I don't think it is possible with the generic Add method you have. Since there is no relation between the generic type argument and the actual argument, there is no way it can determine the generic type through the collection initializer.

An idea could be to add an overload looking something like this:

public void Add(string name, Type t)
{
    _directory.Add(name, t);
}

And then use the collection initializer:

var collection = new FooCollection()
{
    { "Foo", typeof(Foo) },
    { "Bar", typeof(Bar) },
    { "Baz", typeof(Baz) },
};

Unfortunately, as you correctly point out, this eliminate the possibility of adding generic type constraints. Unless you actually provide an argument of the generic type that can be used to infer the type, I don't think this is possible.

MAV
  • 7,260
  • 4
  • 30
  • 47
  • Yes, I was hoping to avoid that which is why I made the class in the first place, else I would just use a `Dictionary` directly. The reason I wanted to avoid that is because then user has do `typeof` which is rather ugly and because then it accepts any type and I can't constrain the type to `Foo` or any class that derives from `Foo`. – Fred Oct 11 '16 at 18:20
  • I see your point. Unfortunately, I don't know of a solution that would allow this constraint at compile time. As mentioned, you need some way to specify the type and a standard collection initializer don't allow this. – MAV Oct 11 '16 at 18:26