Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a condition within a collection initializer

Tags:

c#

dictionary

I have a few values that I want to put into a Dictionary:

// Pretend these values are retrieved from a database or something
string firstName = "John";
string lastName = "Smith";

List<string> listHobbies = new List<string> () {"hockey","soccer"};

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName},
    {"hobbies", listHobbies}
};

However, in some instances, the List<string> variable may be an empty list. If this is the case, I do not want to add the {"hobbies", listHobbies} key to the resulting dictionary.

Is there a way to implement this check within the collection initializer of the dictionary? or am I stuck using an if statement to check the size of the list and remove the key immediately after the declaration?

Edit: Essentially, I am wondering if C# allows something like this:

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName},
    {"hobbies", listHobbies} unless (listHobbies.Count == 0) // don't add the "hobbies" key if the list is empty.
};

I can currently achieve this by doing something like this:

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName},
    {"hobbies", listHobbies}
};
}.Concat(listHobbies != 0 ? new Dictionary<string, dynamic>() { { "hobbies", listHobbies } } : new Dictionary<string, dynamic>()).ToDictionary(k => k.Key, v => v.Value);

But this is incredibly ugly.

Edit 2: I've found a somewhat acceptable solution, if anyone else can offer a nicer looking alternative, feel free to suggest:

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName},
    {"hobbies", !listHobbies.Any() ? listHobbies : null}
}
.Where(entry => entry.Value != null)
.ToDictionary(k => k.Key, v => v.Value);
like image 243
Fano Avatar asked Apr 11 '14 04:04

Fano


People also ask

What is collection initializer?

C# 3.0 (. NET 3.5) introduced Object Initializer Syntax, a new way to initialize an object of a class or collection. Object initializers allow you to assign values to the fields or properties at the time of creating an object without invoking a constructor.

How do you initialize an object in C#?

In object initializer, you can initialize the value to the fields or properties of a class at the time of creating an object without calling a constructor. In this syntax, you can create an object and then this syntax initializes the freshly created object with its properties, to the variable in the assignment.

What is initializer syntax?

The object initializers syntax allows you to create an instance, and after that it assigns the newly created object, with its assigned properties, to the variable in the assignment.


2 Answers

You can

C#'s collection-initializers are just syntactic-sugar over any method named Add, including extension-methods. Whatever C# syntax you have between each-item's {} will be translated into Add method parameters.

So these two statements are almost identical (There is a slight difference in collection-object-lifetime if an exception is thrown before the initializer completes, but that's not worth worrying about):

// Manually:
Dictionary<String,Foo> dict = new Dictionary<String,Foo>();
dict.Add( "foo1", new Foo(1) );
dict.Add( "foo2", new Foo(2) );
dict.Add( "foo3", new Foo(3) );
dict.Add( "foo4", new Foo(4) );

// Collection-initializer:
Dictionary<String,Foo> dict = new Dictionary<String,Foo>()
{
    { "foo1", new Foo(1) },
    { "foo2", new Foo(2) },
    { "foo3", new Foo(3) },
    { "foo4", new Foo(4) }
};

(This also means that if you thought object-initializers were somehow more-efficient than repetitive calls to .Add() or that they'd even result in C++ constexpr-style compile-time pre-initialization of a collection... well unfortunately you're wrong: collection-initializers always have at least O(n) runtime - if you need more efficient initialization consider using an array-backed data-structure so you can use a pre-init array).

So if you want a conditional initializer, first define the extension method for your collection-type (in this case Dictionary<String,dynamic>) with an extra Boolean condition parameter:

public static class MyDictionaryExtensions
{
    public static void Add( this Dictionary<String,dynamic> dict, String key, dynamic value, Boolean condition )
    {
        if( condition )
        {
            dict.Add( key, value );
        }
    }
}

Then you can use it like so:

// Pretend these values are retrieved from a database or something
string firstName = "John";
string lastName = "Smith";

List<string> listHobbies = new List<string> () {"hockey","soccer"};

var dict = new Dictionary<string, dynamic>() 
{
    { "firstName", firstName   },
    { "lastName" , lastName    },
    { "hobbies"  , listHobbies, listHobbies.Count > 0 }
};

Better yet: you can make it generic (and you probably shouldn't be using dynamic anyway):

public static class MyDictionaryExtensions
{
    public static void Add<TKey,TValue>( this Dictionary<TKey,TValue> dict, TKey key, TValue value, Boolean condition )
        where TKey : notnull
    {
        if( condition )
        {
            dict.Add( key, value );
        }
    }
}

Here's screenshot proof of it working in LinqPad:

enter image description here


But why stop there? You're not limited to Boolean condition parameters: you could have some gnarly logic that inspects the state of the this dict, for example, or use a Func<Boolean> to determine the condition at runtime for each item.

There is a minor caveat though: your Add extension method's parameters cannot exactly match the original Add method, otherwise your extension-method won't be selected due to how extension-methods always have lower precedence. You also cannot force-select your extension-method by using an explicit parameter name (collection-initializers don't have named parameters), but you could use a dummy parameter as a third-parameter using a custom struct type (so it has effectively zero cost).

For example, here's an extension I use to succinctly populate Exception.Data's dictionary with information from a failed HttpClient request, which skips responseBody if the value is null:

internal struct OnlyIfBodyIsNotNull
{
}

internal static class DictionaryExtensions
{
    public static void Add( this IDictionary exDataDict, String key, String? body, OnlyIfBodyIsNotNull _ )
    {
        if( body != null )
        {
            exDataDict.Add( key, body );
        }
    }
}

Used like so:

public static InvalidOperationException CreateException( String message, HttpResponseHeaders responseHeaders, HttpContentHeaders contentHeaders, String? responseBody = null )
{
    return new InvalidOperationException( message )
    {
        Data =
        {
            { "Response.Headers"       , responseHeaders.ToString() },
            { "Response.ContentHeaders", contentHeaders .ToString() },
            { "Response.Body"          , responseBody              , default(OnlyIfBodyIsNotNull) }
        }
    };
}
like image 103
Dai Avatar answered Oct 19 '22 19:10

Dai


What's wrong with this?

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName}
};
if (listHobbies.Any())
    dict.Add("hobbies", listHobbies);
like image 1
jomsk1e Avatar answered Oct 19 '22 21:10

jomsk1e