Dmitry Volkov
Dmitry Volkov

Reputation: 1337

LINQ to dictionary with multiple keys for a single value

Consider the following code:

public KeyAttribute : Attribute{
    public string Value;
    public KeyAttribute(string value){
        Value = value;
    }
}

[Key("A")]
[Key("AB")]
public class A : IClass
{
    public string FieldA {get;set;}
}

[Key("B")]
public class B : IClass
{
    public string FieldB {get;set;}
}

So I have to through all the implementations of IClass interface, and I've got to construct a dictionary Dictionary<string, ConstructorInfo> where the keys are the Value properties of KeyAttribute, and the values are the corresponding ConstructorInfo.
Notice that there might be several KeyAttribute on a single class, so for that case, there should be the corresponding amount of entries in the dictionary.

For the current example, the desired outcome is:

key     | value
--------+-------------------
"A"     | ConstructorInfo A
"AB"    | ConstructorInfo A
"B"     | ConstructorInfo B

At first I wrote this:

return AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s => s.GetTypes())
    .Where(t => typeof(IClass).IsAssignableFrom(t))
    .ToDictionary(t =>
    {
        var key = (KeyAttribute) t.GetCustomAttributes(typeof(KeyAttribute), true).First();
        return key.Value;
    }, t => t.GetConstructors(BindingFlags.Public).First());

But as you can see, the code above does not handle the situation with several attributes. So I did the following, but it's obviously wrong and I'm not sure how to correct it.

return AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s => s.GetTypes())
    .Where(t => typeof(IClass).IsAssignableFrom(t))
    .ToDictionary(t =>
    {
        return t.GetCustomAttributes(typeof(KeyAttribute), true).Select(a => ((KeyAttribute)a).Value);
    }, t => t.GetConstructors(BindingFlags.Public).First());

I know I can do that without LINQ like that:

var types = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s => s.GetTypes())
    .Where(t => typeof(IClass).IsAssignableFrom(t));
var dict = new Dictionary<string, ConstructorInfo>();
foreach (var type in types)
{
    var keys = type.GetCustomAttributes(typeof(KeyAttribute), true).Cast<KeyAttribute>().Select(a => a.Value);
    var ctorInfo = type.GetConstructors(BindingFlags.Public).First();
    foreach (var key in keys)
    {
        dict.Add(key, ctorInfo);
    }
}
return dict;

But I'd rather stick to LINQ, if it is possible.

Sorry about all these somewhat misleading details about attributes and all that, while it is a question about LINQ, but I couldn't think of any other vivid example.

Upvotes: 1

Views: 3813

Answers (2)

Fabio
Fabio

Reputation: 32445

Use SelectMany for key attributes

return AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(t => typeof(IClass).IsAssignableFrom(t))
                .SelectMany(t => 
                {
                    return t.GetCustomAttributes<KeyAttribute>()
                            .Select(ka => new
                            {
                                Key = ka,
                                Ctor = t.GetConstructors(BindingFlags.Public).First() 
                            });
                }) 
                .ToDictionary(t => t.Key, t.Ctor);

Upvotes: 1

vc 74
vc 74

Reputation: 38179

This should work:

return AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s => s.GetTypes())
    .Where(t => typeof(IClass).IsAssignableFrom(t))
    .SelectMany(t => t.GetCustomAttributes(typeof(KeyAttribute), true)
        .Select(a => new
        {
            constInfo = t.GetConstructors(BindingFlags.Public).First(),
            attrVal = ((KeyAttribute)a).Value 
        }))
    .ToDictionary(entry => entry.attrVal, entry => entry.constInfo);

Upvotes: 4

Related Questions