How to make inline array initialization work like e.g. Dictionary initialization?

后端 未结 6 1801
滥情空心
滥情空心 2021-02-12 12:02

Why is it possible to initialize a Dictionary like this:

var dict = new Dictionary() { 
    { \"key1\", 1 },
    { \"         


        
相关标签:
6条回答
  • 2021-02-12 12:13

    The collection initializer syntax is translated into calls to Add with the appropriate number of parameters:

    var dict = new Dictionary<string,int>();
    dict.Add("key1", 1);
    dict.Add("key2", 2);
    

    This special initializer syntax will also work on other classes that have an Add method and implements IEnumerable. Let's create a completely crazy class just to prove that there's nothing special about Dictionary and that this syntax can work for any suitable class:

    // Don't do this in production code!
    class CrazyAdd : IEnumerable
    {
        public void Add(int x, int y, int z)
        {
            Console.WriteLine(x + y + z); // Well it *does* add...
        }
    
        public IEnumerator GetEnumerator() { throw new NotImplementedException(); }
    }
    

    Now you can write this:

    var crazyAdd = new CrazyAdd
    {
        {1, 2, 3},
        {4, 5, 6}
    };
    

    Outputs:

    6
    15
    

    See it working online: ideone

    As for the other types you asked about:

    • It doesn't work on an array because it has no Add method.
    • List<T> has an Add method but it has only one parameter.
    0 讨论(0)
  • 2021-02-12 12:15

    Your problem stems from the fact that it is an array, not a collection.

    var kvps = new KeyValuePair<string,int>[] {
        { "key1", 1 },
        { "key2", 2 }
    };
    

    should really be:

    var kvps = new KeyValuePair<string, int>[] {
        new KeyValuePair<string, int>("key1", 1),
        new KeyValuePair<string, int>("key2", 2)
    };
    

    The giveaway is the brackets. [] is an array. {} is a collection.

    0 讨论(0)
  • 2021-02-12 12:19

    It does work with the Dictionary, because it has an overload for Add that takes two arguments. Arrays dont even have an Add method, let alone one with two arguments.

    The Dictionary class is specially designed to work with KeyValuePair<,> internally, that is the only reason you do not need the call the constructor manually, instead the two-argument Add is called and constructs the KeyValuePair under the hood.

    Every other IEnumerable<KeyValuePair<,>> does not have this special implementation and therefore has to be initialized this way:

    var array = new KeyValuePair<int, int>[] {
        new KeyValuePair<int, int>(1, 2),
        new KeyValuePair<int, int>(3, 4)
    };
    

    You can create the same behaviour with your own classes, like lets say you implement this:

    class ThreeTupleList<T1, T2, T3> : List<Tuple<T1, T2, T3>>
    {
        public void Add(T1 a, T2 b, T3 c)
        {
            this.Add(new Tuple<T1, T2, T3>(a, b, c));
        }
    
        // You can even implement a two-argument Add and mix initializers
        public void Add(T1 a, T2 b)
        {
            this.Add(new Tuple<T1, T2, T3>(a, b, default(T3)));
        }
    }
    

    you can initialize it like this, and even mix three-, two- and one-argument initializers:

    var mylist = new ThreeTupleList<int, string, double>()
    {
        { 1, "foo", 2.3 },
        { 4, "bar", 5.6 },
        { 7, "no double here" },
        null
    };
    
    0 讨论(0)
  • 2021-02-12 12:20

    You might not see it but it is the same syntax. The only difference is that an array takes elements as an input where a dictionary takes a 2 dimensional array.

    int[] a = new int[]{5,6};
    int[,] a = new int[]{{5,6},{3,6}};
    Dictionary<int,int> a = new Dictionary<int,int>{{5,6},{3,6}};
    
    0 讨论(0)
  • 2021-02-12 12:28

    Override IDictionary for the C# 6.0 syntax

    In case someone comes here, as I did, looking to save some keystrokes for the new C# 6.0 dictionary initializer syntax, it can be done, but requires deriving from IDictionary instead. You only need to implement the this[] set method to get this to work, which leaves a ton of non-implemented methods.

    The class implementation looks like this:

    // Seriously!  Don't do this in production code! Ever!!!
    public class CrazyAdd2 : IDictionary<string, int>
    {
        public int this[string key]
        {
            get { throw new NotImplementedException(); }
            set {Console.WriteLine($"([{key}]={value})"); }
        }
    
    #region NotImplemented
    // lots of empty methods go here
    #endregion
    }
    

    then to use it:

    var crazyAdd2 = new CrazyAdd2
    {
        ["one"] = 1,
        ["two"] = 2,
    };
    

    and the output:

    ([one]=1)
    ([two]=2)
    

    And here's a fiddle demonstrating the whole thing:

    https://dotnetfiddle.net/Lovy7m

    0 讨论(0)
  • 2021-02-12 12:30

    Thanks to multiple answerers for pointing out that the Add method is the magical thing secret sauce that makes the initialization syntax work. So I could achieve my goal by inheriting the class in question (KeyValuePair):

    public class InitializableKVPs<T1,T2> : IEnumerable<KeyValuePair<T1,T2>>
    {
        public void Add(T1 key, T2 value) 
        {
            throw new NotImplementedException();
        }
    
        public IEnumerator<KeyValuePair<string,string>>  GetEnumerator()
        {
            throw new NotImplementedException();
        }
    
        IEnumerator  IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }
    

    This now is accepted by the compiler:

    var kvps = new InitializableKVPs<string,int> {
        { "key1", 1 },
        { "key2", 2 }
    };
    

    Edit: Philip Daubmeier's answer has an actual, concise implementation of this.

    0 讨论(0)
提交回复
热议问题