3

Is it possible to create an extension method on a Dictionary, that is able to self-initialize if the Dictionary is null?

I'm thinking something along the lines of the following:

public static void AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
{
    // Initialize the Dictionary if null
    dictionary = dictionary ?? new Dictionary<TKey, TValue>();

    // Update or insert value
    dictionary[key] = value;
}

Practical use example:

// null Dictionary
Dictionary<string, string> dict = null;

// Self-initializing
dict.AddOrUpdate("Key", "Value");

Or as a property:

// Self-initializing
class.nullDict.AddOrUpdate("Key", "Value");
Jones
  • 85
  • 1
  • 10
  • 6
    Maybe, but this breaks the usual semantics of calling a method on a null object. Ordinarily that should throw a `NullReferenceException`. –  Oct 26 '16 at 14:21
  • 3
    @Amy you're not supposed to throw NREs yourself. [Throw an ArgumentNullException](http://stackoverflow.com/questions/463302/argumentnullexception-or-nullreferenceexception-from-extension-method) instead. – CodeCaster Oct 26 '16 at 14:22
  • Yes You you can do this, But You have to make it threadsafe too – Vivek Nuna Oct 26 '16 at 14:22
  • 5
    That's not what I'm talking about, @CodeCaster. Calling a method on a null object should result in a `NullReferenceException`. This is breaking that semantic. I am not suggesting he throw anything. I'm suggesting he not do this. –  Oct 26 '16 at 14:22
  • `throw new ShootInTheFootException();` – Diligent Key Presser Oct 26 '16 at 14:22
  • I don't see how this would work unless you change the return type from `void`to a `Dictionary` as well and assign it on every call. IMO its bad design. – Igor Oct 26 '16 at 14:23
  • 2
    Your if .. else... block is equivalent to dictionary[key] = value – vc 74 Oct 26 '16 at 14:23
  • @CodeCaster I am perfectly aware this is about extension methods. Thanks for filling us in on the obvious. Did you know what when you call a method on a null object, the *expected* result is a `NullReferenceException`? I don't know how I can make this any more clear. Try it now: `string s = null; s = s.Trim();`. What happens? –  Oct 26 '16 at 14:23
  • 1
    Even if this is possible, I agree that this violates 'normal' semantics. If you just looked at your example, I would not expect to end up with a dictionary. I'd expect it to be an error or no-op at best. Just initialize it normally and save anyone else the trouble of figuring it out. If you're trying for lazy initialization, then use `Lazy`. – Glorin Oakenfoot Oct 26 '16 at 14:25
  • @codecaster I'm not saying he should throw anything. I'm saying he shouldn't do this because it breaks the usual null reference semantics. I've said this twice now. –  Oct 26 '16 at 14:28
  • @CodeCaster what Amy is saying is that doing that WILL throw a NRE. while 'should' is the correct word in that sentence, English semantics detract the obvious meaning of the sentence. – Michael Coxon Oct 26 '16 at 14:34
  • 1
    @Amy yes, I pointed out (in my now-removed comment, I really don't want to have a snark-fight at the moment) that you keep repeating yourself already. [.NET allows calling extension methods on null references by design](http://stackoverflow.com/questions/847209/in-c-what-happens-when-you-call-an-extension-method-on-a-null-object), so I'm not really sure what point you're trying to make. Should extension methods be redesigned, or should the OP simply not design this particular method to support nulls in this case? – CodeCaster Oct 26 '16 at 14:35
  • @CodeCaster I'll let someone else try to explain my point to you. All I can do at this point is repeat "don't do this, it breaks null reference semantics of the dot operator". Yes I know the language permits this. Just because its permitted doesn't mean its a good idea, however. –  Oct 26 '16 at 14:38
  • No, I'll let someone else try to explain it to you. If you aren't sure what point I'm trying to make, after I've said it three times, I don't think I'm able to help you. –  Oct 26 '16 at 14:41

6 Answers6

5

No

For this to work, you would have to pass your parameter by ref and that is not allowed.

When the this keyword modifies the first parameter of a static method, it signals to the compiler that the method is an extension method. No other modifiers are needed or allowed on the first parameter of an extension method.

Community
  • 1
  • 1
nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • Is this true even when using an attribute to indicate an extension method, instead of using C#'s `this` syntax? – Zev Spitz Oct 26 '16 at 15:11
  • @Zev is there an attribute to indicate extension methods? AFAIK the `(this Foo bar)` parameter syntax is the only way to indicate an extension method. – CodeCaster Oct 26 '16 at 15:13
  • 1
    @CodeCaster [System.Runtime.CompilerServices.ExtensionAttribute](https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.extensionattribute(v=vs.110).aspx). It's the only way to define extension methods in VB.NET which doesn't have a dedicated extension method syntax. Presumably the compiler applies the attribute when the C# `this` syntax is used. – Zev Spitz Oct 26 '16 at 15:15
  • @Zev alright, TIL. But using it gives a compiler error in C# (_"Do not use `System.Runtime.CompilerServices.ExtensionAttribute` directly. Use parameter modifier `this' instead"_). – CodeCaster Oct 26 '16 at 15:15
  • @CodeCaster Submitted [an issue](https://github.com/dotnet/roslyn/issues/14747) at the Roslyn project on Github. – Zev Spitz Oct 26 '16 at 16:15
4

What you're asking for is discussed in How can I get an extension method to change the original object?, and the outcome is: in the case of a reference type, all you can do is manipulate the object. You can't assign it, because then you'll be working on a local copy and not the object originally passed into the method:

public static void Foo(this Object bar)
{
    // assignment
    bar = new ...();

    // here you're NOT calling Baz() on the object passed into the method
    bar.Baz();
}

Note that those are reference type semantics. You can't even do anything meaningful to value types:

public static void PlusOne(this int number)
{
    number++;
}

This will never modify the original value, as those are passed by value by definition: this extension method operates on a copy of the value it was called with.

That being said, this is more a question about expectations. Anyone reading this code:

Dictionary<string, string> dict = null;
dict.AddOrUpdate("Key", "Value");

Would expect either a NullReferenceException (if AddOrUpdate() were a member method of Dictionary<TKey, TValue>) or an ArgumentNullException in the case of an extension method, and even the compiler would expect that.

There's an underlying reason you think you need this. Sure, implementing the answers that say "return the new or updated dictionary from the extension method and assign at every modification" works, but gives you an awfully clumsy syntax:

Dictionary<string, string> dict = null;
dict = dict.AddOrUpdate("Key", "Value");

And you'll only have to forget the unusual dict = once to get a NRE/ANE at some point beyond its usage. That's maybe fine if you're into functional programming, but C# is not that.

So: make sure that the dictionary is initialized before trying to add key-value pairs and the need for all this unusualness goes away. It may be as simple as this:

Dictionary<string, string> dict = possiblyNullVariable ?? new Dictionary<string, string>();
Community
  • 1
  • 1
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
1

I would think that this is very confusing for everyone who is using it, since performing operations on null objects is not something common.

So even if it's possible, I would rather use

Dictionary<string, string> dict = new Dictionary<string, string>();

Moreover it even works with properties now.

Ilya Chernomordik
  • 27,817
  • 27
  • 121
  • 207
1

You could but you would have to change the return type from void to Dictionay which would be a very poor design IMO. The caller would then have to execute an assignment every time the extension is used.

public static IDictionary<TKey, TValue> AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
{
    // Initialize the Dictionary if null
    if (dictionary == null)
        dictionary = new Dictionary<TKey, TValue>();

    // Update value if key already exists
    if (dictionary.ContainsKey(key))
        dictionary[key] = value;
    else
        // Insert value with the given key
        dictionary.Add(key, value);

    return dictionary;
}
Igor
  • 60,821
  • 10
  • 100
  • 175
1

I suggest changing method's signature:

// Let's return the dictionary (either passed or created) 
public static IDictionary<TKey, TValue> AddOrUpdate<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary, 
    TKey key, 
    TValue value) {

    IDictionary<TKey, TValue> result = dictionary;

    // Initialize the Dictionary if null
    if (result == null)
        result = new Dictionary<TKey, TValue>();

    // Update value if key already exists
    if (result.ContainsKey(key))
        result[key] = value;
    else
        // Insert value with the given key
        result.Add(key, value);

    return result;
}

...

Dictionary<string, string> dict = null;

dict = dict.AddOrUpdate("Key", "Value");
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
1

I think what you want is a self-initializing property:

private Dictionary<string, string> dict;

public Dictionary<string, string> Dict => this.dict ?? (this.dict = new Dictionary<string, string>());

Then, whenever you use Dict, it's guaranteed to be initialized.

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59