Reputation: 2737
I am trying to build a dictionary where the key is a property of the value object. However I would like to construct the value object in the dictionary's add method. Is there a way to do this without using an intermediate variable?
For example I would like to do the following, but of course the key value isn't available when needed.
Dictionary<int,SomeComplexObject> dict = new Dicionary<int,SomeComplexObject>{
{someComplexObject.Key, new SomeComplexObject {Key = 1, Name = "FooBar"},
{someComplexObject.Key, new SomeComplexObject {Key = 2, Name = "FizzBang"}
};
Do I have to do it this ugly way:
Dictionary<int,SomeComplexObject> dict = new Dicionary<int,SomeComplexObject>();
SomeComplexObject value1 = new SomeComplexObject{Key=1,Name = "FooBar"};
dict.Add(value1.Key, value1);
SomeComplexObject value2 = new SomeComplexObject{Key=2,Name = "FizzBang"};
dict.Add(value2.Key, value2);
I don't think this is the same question as How to use an object's identity as key for Dictionary<K,V>
because I am not asking specifically about the key of a dictionary, but if there is a way to have access to a objects property when the object is not being created until later in the methods parameter list.
Upvotes: 7
Views: 5857
Reputation: 79
I know the question is a few years old by now, but I came across it having had the same problem. This is what I ended up doing, maybe it helps someone:
public class MyClass
{
public string Name { get; set; }
public int SomeInt { get; set; }
}
public class DictionaryCreator
{
public Dictionary<string, MyClass> CreateDictionary()
{
// the one required instance variable
MyClass myObject;
Dictionary<string, MyClass> result = new Dictionary<string, MyClass>()
{
{ (myObject = new MyClass() {Name = "a", SomeInt = 1}).Name, myObject },
{ (myObject = new MyClass() {Name = "b", SomeInt = 2}).Name, myObject },
{ (myObject = new MyClass() {Name = "c", SomeInt = 3}).Name, myObject },
...
};
return result;
}
}
The precedence of the () expression ensures the object exists before it is used.
Upvotes: 0
Reputation: 70652
I don't think an extension method (as proposed in comments) is really what you want here, as it's only a partial solution. I.e. you would have to write a new extension method for each dictionary value type you wanted to use, which negates the whole point of asking for a general solution.
Instead, it seems to me that you probably just want to subclass the Dictionary<TKey, TValue>
type to add your own custom behavior. You can do this just once, in a general-purpose way, so that you can provide a delegate for each type you expect to have to use this way.
That would look something like this:
class KeyExtractorDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
private readonly Func<TValue, TKey> _extractor;
public KeyExtractorDictionary(Func<TValue, TKey> extractor)
{
_extractor = extractor;
}
public void Add(TValue value)
{
Add(_extractor(value), value);
}
}
You would use it something like this:
class Data
{
public int Key { get; }
public string Name { get; }
public Data(int key, string name)
{
Key = key;
Name = name;
}
}
class Program
{
static void Main(string[] args)
{
KeyExtractorDictionary<int, Data> dictionary =
new KeyExtractorDictionary<int, Data>(d => d.Key)
{
new Data(1, "FooBar"),
new Data(2, "FizzBang")
};
}
}
(I used Data
as the value type type, instead of T
as you seem to have used in your question, to avoid confusing the type name with a generic type parameter.)
In this way, you only have to write the class once, regardless of how many different types you might want to use for this type of dictionary. You can then pass the class constructor the key extractor delegate appropriate for the current value type of the dictionary.
Note that doing it this way, you also can take advantage of C#'s collection initializer syntax. Since your new type has an Add()
method that takes just the value for each dictionary entry, the compiler will translate a collection initializer into the correct calls to add each object to the dictionary.
This allows for a dictionary in which you can still retrieve objects solely by the key value (using a custom comparer would require an instance of the value type with the same key you're looking for), while still addressing the broader concerns of not having to specify the key explicitly when adding objects, and of generality and reuse.
Upvotes: 5
Reputation: 31
You can use the ToDictionary() extension method to solve this issue. Here is a complete example that can be run in LINQPad.
void Main()
{
Dictionary<int, SomeComplextObject> dict = new List<SomeComplextObject>{
{new SomeComplextObject {Key = 1, Name = "FooBar"}},
{new SomeComplextObject {Key = 2, Name = "FizzBangr"}}
}.ToDictionary(k =>k.Key);
//Dump Dictionary to LINQPad's result window.
dict.Dump();
}
public class SomeComplextObject{
public int Key { get; set; }
public string Name {get;set;}
}
Upvotes: 3
Reputation: 309
I would like to propose a different slightly different way of going about this, it's similar to @pid 's method but instead of an interface uses a linq expression. First built your list of objects, then use an extension method to add them to your dictionary in a single simple step. In my mind this is also a little more intuitive to read, your program would look like:
class Program
{
static void Main(string[] args)
{
List<SomeComplexObject> toAdd = new List<SomeComplexObject>() {
new SomeComplexObject(1,"FooBar"),
new SomeComplexObject(2,"FizzBang")
};
var dict = new Dictionary<int,SomeComplexObject>();
dict.AddByKey(toAdd, item => item.Key);
}
}
Where AddByKey is an extension method that uses linq to basically pass a reference to that property and would look like this:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
public static class DictionaryExtensions
{
/// <summary>
/// This extension method was built for when you want to add a list of items to a dictionary as the values, and you want to use one of those
/// items' properties as the key. It uses LINQ to check by property reference.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dict"></param>
/// <param name="targets"></param>
/// <param name="propertyToAdd"></param>
public static void AddByKey<TKey, TValue>(this Dictionary<TKey, TValue> dict, IEnumerable<TValue> targets, Expression<Func<TValue, TKey>> propertyToAdd)
{
MemberExpression expr = (MemberExpression)propertyToAdd.Body;
PropertyInfo prop = (PropertyInfo)expr.Member;
foreach (var target in targets)
{
var value = prop.GetValue(target);
if (!(value is TKey))
throw new Exception("Value type does not match the key type.");//shouldn't happen.
dict.Add((TKey)value, target);
}
}
}
And if you wanted to simplify the calling code even further, instead of returning void, you could have that extension method return the originally passed dictionary, and then your calling code could be collapsed to:
var dict = new Dictionary<int,SomeComplexObject>().AddByKey(toAdd, item => item.Key);
Upvotes: 0
Reputation: 11597
You can try an extension method, which is less invasive:
public static void AddByKey<TKey, T>(this Dictionary<TKey, T> dictionary, T item)
{
dictionary.Add(item.Key, item);
}
But to really do this correctly you also need an interface to protect you against types without the Key
property:
public interface ItemWithKey<TKey>
{
TKey Key { get; }
}
public static void AddByKey<TKey, T>(this Dictionary<TKey, T> dictionary, T item)
where T : ItemWithKey<TKey>
{
dictionary.Add(item.Key, item);
}
I don't have a compiler in my hands right now, I cannot test this code so minor errors may have slipped in. I hope you get the idea and usefulness if you have those cases a lot in your code. Otherwise, I'd advise to go with the ugly working code you already have.
Upvotes: 2