How to initialize a ConcurrentDictionary? Error: "Cannot access private method 'Add' here"
Asked Answered
T

5

22

I have a static class in which I am using dictionaries as lookup tables to map between .NET types and SQL types. Here is an example of such a dictionary:

private static readonly Dictionary<Type, string> SqlServerMap = new Dictionary<Type, string>
{
    {typeof (Boolean), "bit"},
    {typeof (Byte[]), "varbinary(max)"},
    {typeof (Double), "float"},
    {typeof (Byte), "tinyint"},
    {typeof (Int16), "smallint"},
    {typeof (Int32), "int"},
    {typeof (Int64), "bigint"},
    {typeof (Decimal), "decimal"},
    {typeof (Single), "real"},
    {typeof (DateTime), "datetime2(7)"},
    {typeof (TimeSpan), "time"},
    {typeof (String), "nvarchar(MAX)"},
    {typeof (Guid), "uniqueidentifier"}
};

Then I have a public method below which passes in a .NET type and it returns the string value of the corresponding MS SQL Server type using this dictionary. However, since this is being used as a lookup table for making database queries, I think it makes sense to make it a ConcurrentDictionary. I changed it to:

private static readonly IDictionary<Type, string> SqlServerMap = new ConcurrentDictionary<Type, string>
{
    {typeof (Boolean), "bit"},
    {typeof (Byte[]), "varbinary(max)"},
    {typeof (Double), "float"},
    {typeof (Byte), "tinyint"},
    {typeof (Int16), "smallint"},
    {typeof (Int32), "int"},
    {typeof (Int64), "bigint"},
    {typeof (Decimal), "decimal"},
    {typeof (Single), "real"},
    {typeof (DateTime), "datetime2(7)"},
    {typeof (TimeSpan), "time"},
    {typeof (String), "nvarchar(MAX)"},
    {typeof (Guid), "uniqueidentifier"}
};

But now it underlines everything red within the {} (i.e. all key value pairs of the ConcurrentDictionary) and the error is:

Cannot access private method 'Add' here

I don't think it's because I initialize it as private static readonly, because I just tested by making a public static version and I got the same error.

Trawl answered 4/8, 2015 at 17:18 Comment(1)
As your collection is not going to change, you could now use the ImmutableDictionary. While this also has issues with the initialize, there are solutions.Cyst
C
17

The collection initializer that you're using to populate the collection only works if the collection has an Add method of an appropriate signature and accessibility. ConcurrentDictionary doesn't have a public Add method, so you won't be able to use a collection initializer with it.

You can provide some initial data by passing an IEnumerable<KeyValuePair<TKey, TValue>> as a parameter to the constructor, or you can call TryAdd (or AddOrUpdate, or any of the other methods with Add in the name) in a loop after creating the ConcurrentDictionary.

Crossbeam answered 4/8, 2015 at 17:22 Comment(3)
Ah ok I see, thanks. I wish I could just have it already initialized in a static ConcurrentDictionary since I know it's not going to change and I just want to access it quickly. I'll see what I can do though.Trawl
@Trawl And you can, by passing an IEnumerable to the constructor, rather than using a collection initializer.Crossbeam
Ohh I misunderstood what you meant in your answer, thanks!Trawl
A
49

Try this

private static readonly IDictionary<Type, string> SqlServerMap =
    new ConcurrentDictionary<Type, string>(
        new Dictionary<Type, string>()
        {
            {typeof(Boolean ), "bit"             },
            {typeof(Byte[]  ), "varbinary(max)"  },
            {typeof(Double  ), "float"           },
            {typeof(Byte    ), "tinyint"         },
            {typeof(Int16   ), "smallint"        },
            {typeof(Int32   ), "int"             },
            {typeof(Int64   ), "bigint"          },
            {typeof(Decimal ), "decimal"         },
            {typeof(Single  ), "real"            },
            {typeof(DateTime), "datetime2(7)"    },
            {typeof(TimeSpan), "time"            },
            {typeof(String  ), "nvarchar(MAX)"   },
            {typeof(Guid    ), "uniqueidentifier"}
        }
    );

Updated: if you are using C#6(Roslyn 2.0 Compiler), you can use the new Dictionary Initializers.

private static readonly IDictionary<Type, string> SqlServerMap =
    new ConcurrentDictionary<Type, string>
    {
        [typeof(Boolean )] = "bit"             ,
        [typeof(Byte[]  )] = "varbinary(max)"  ,
        [typeof(Double  )] = "float"           ,
        [typeof(Byte    )] = "tinyint"         ,
        [typeof(Int16   )] = "smallint"        ,
        [typeof(Int32   )] = "int"             ,
        [typeof(Int64   )] = "bigint"          ,
        [typeof(Decimal )] = "decimal"         ,
        [typeof(Single  )] = "real"            ,
        [typeof(DateTime)] = "datetime2(7)"    ,
        [typeof(TimeSpan)] = "time"            ,
        [typeof(String  )] = "nvarchar(MAX)"   ,
        [typeof(Guid    )] = "uniqueidentifier"
    };

Example https://dotnetfiddle.net/9ZgjsR

Automata answered 30/1, 2017 at 0:59 Comment(0)
C
17

The collection initializer that you're using to populate the collection only works if the collection has an Add method of an appropriate signature and accessibility. ConcurrentDictionary doesn't have a public Add method, so you won't be able to use a collection initializer with it.

You can provide some initial data by passing an IEnumerable<KeyValuePair<TKey, TValue>> as a parameter to the constructor, or you can call TryAdd (or AddOrUpdate, or any of the other methods with Add in the name) in a loop after creating the ConcurrentDictionary.

Crossbeam answered 4/8, 2015 at 17:22 Comment(3)
Ah ok I see, thanks. I wish I could just have it already initialized in a static ConcurrentDictionary since I know it's not going to change and I just want to access it quickly. I'll see what I can do though.Trawl
@Trawl And you can, by passing an IEnumerable to the constructor, rather than using a collection initializer.Crossbeam
Ohh I misunderstood what you meant in your answer, thanks!Trawl
S
5

As a code example to Servy's accepted answer, in order to initialize a ConcurrentDictionary at instantiation, you can pass a type that impements IEnumerable (like a List) of KeyValuePair types to the constructor:

private static readonly IDictionary<Type, string> SqlServerMap =
    new ConcurrentDictionary<Type, string>(
        new List<KeyValuePair<Type, string>>
        {
            new KeyValuePair<Type, string>(typeof(Boolean), "bit"),
            new KeyValuePair<Type, string>(typeof(Boolean), "bit"),
            new KeyValuePair<Type, string>(typeof(Byte[]), "varbinary(max)"),
            new KeyValuePair<Type, string>(typeof(Double), "float"),
            new KeyValuePair<Type, string>(typeof(Byte), "tinyint"),
            new KeyValuePair<Type, string>(typeof(Int16), "smallint"),
            new KeyValuePair<Type, string>(typeof(Int32), "int"),
            new KeyValuePair<Type, string>(typeof(Int64), "bigint"),
            new KeyValuePair<Type, string>(typeof(Decimal), "decimal"),
            new KeyValuePair<Type, string>(typeof(Single), "real"),
            new KeyValuePair<Type, string>(typeof(DateTime), "datetime2(7)"),
            new KeyValuePair<Type, string>(typeof(TimeSpan), "time"),
            new KeyValuePair<Type, string>(typeof(String), "nvarchar(MAX)"),
            new KeyValuePair<Type, string>(typeof(Guid), "uniqueidentifier")
        });
Salvidor answered 20/3, 2017 at 5:33 Comment(0)
C
3

As your collection is not going to change, you could now use the ImmutableDictionary. While this also has issues with the initialize, there are solutions proposed for this question about initialization:

A simple solution provided by @LukášLánský is

var d = new Dictionary<string, int> { { "a", 1 }, { "b", 2 } }.ToImmutableDictionary();

and a better performing version provided by @IanGriffiths is

public struct MyDictionaryBuilder<TKey, TValue> : IEnumerable
{
    private ImmutableDictionary<TKey, TValue>.Builder _builder;

    public MyDictionaryBuilder(int dummy)
    {
        _builder = ImmutableDictionary.CreateBuilder<TKey, TValue>();
    }

    public void Add(TKey key, TValue value) => _builder.Add(key, value);

    public TValue this[TKey key]
    {
        set { _builder[key] = value; }
    }

    public ImmutableDictionary<TKey, TValue> ToImmutable() => _builder.ToImmutable();

    public IEnumerator GetEnumerator()
    {
        // Only implementing IEnumerable because collection initializer
        // syntax is unavailable if you don't.
    throw new NotImplementedException();
    }
}
Cyst answered 4/7, 2018 at 16:55 Comment(0)
A
0

As said @Servy in his answer, collection initialization works for types with Add method. But also it should work, if extension method with name Add and appropriate signature exists. So you could create one for concurrent dictionary. Initialization will be thread-safe, due to fact you are using static field initializer.

Ashurbanipal answered 4/8, 2015 at 17:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.