Lucas Niewohner
Lucas Niewohner

Reputation: 337

Mapping a single-method-to-multiple-strings Dictionary to a single-string-to-multiple-method Dictionary

I Dare say that's the most complicated title I've ever come up with. I'm just not sure how to better explain it, short of this example:

I have a type. Under this type, I have assorted methods, some of which have one or more "OnAttribute" attributes attached to them, each defining an event name this method needs to fire on. I'm currently getting a Dictionary of Methods to their relevant lists of event names with this query:

var methods = this.GetType().GetMethods()
    .Where(m => Attribute.IsDefined(m, typeof(OnAttribute)))
    .ToDictionary(m => m,
        m => m.GetCustomAttributes(typeof(OnAttribute), true)
            .Select(a => ((OnAttribute)a).EventName)); //OnAttribute defines an EventName field

The resulting type is IEnumerable<IGrouping<System.Reflection.MethodInfo,IEnumerable<string>>>. However, in order to sign the methods up for their requested events, I need to map each event name to any and all methods asking to execute on them. In other words, I need the previously mentioned type mapped to IEnumerable<IGrouping<string,IEnumerable<System.Reflection.MethodInfo>>>.

I need this:

[On("Event_1")]
[On("Event_2")]
[On("Event_4")]
void Method_1()
{
    ...
}

[On("Event_1")]
void Method_2()
{
     ...
}

[On("Event_2")]
[On("Event_3")]
void Method_3()
{
    ...
}

Which, using my current code, would map to this:

Method_1 => ["Event_1","Event_2","Event_4"]
Method_2 => ["Event_1"]
Method_3 => ["Event_2","Event_3"]

Instead mapped to this:

"Event_1" => [Method_1,Method_2]
"Event_2" => [Method_1,Method_3]
"Event_3" => [Method_2]
"Event_4" => [Method_1]

I'm sure there's some better terminology out there somewhere to describe this; I just don't know what it is.

I attempted using a foreach loop to create a dictionary the ugly way, but as I said, that's really ugly. I also attempted to use SelectMany() with some anonymous types to flatten the Dictionary into a list, and then use GroupBy to re-group that list into my desired dictionary, but that seemed inefficient and messy. I'd like to know the (best) LINQ way of doing this.

I hope that made at least a little bit of sense.

Thanks for any help!

Upvotes: 0

Views: 97

Answers (2)

Brian Rogers
Brian Rogers

Reputation: 129777

To get the reverse mapping from your current dictionary of methods, you can do this:

var reverseMapping = methods
    .SelectMany(kvp => kvp.Value, (kvp, evt) => new { Event = evt, Method = kvp.Key })
    .GroupBy(a => a.Event, a => a.Method);

Then, if you want to dump them out:

foreach (var grouping in reverseMapping.OrderBy(g => g.Key))
{
    Console.WriteLine(string.Format("{0} => {1}", 
        grouping.Key, string.Join(",", grouping.Select(m => m.Name))));
}

Fiddle: https://dotnetfiddle.net/SdE7LD

Upvotes: 0

sachin
sachin

Reputation: 2361

You have to use a combination of SelectMany and GroupBy to get the required result.

Here is a solution that seems to be working:

var methods = this.GetType().GetMethods()
    .Where(m => Attribute.IsDefined(m, typeof(OnAttribute)))
    .SelectMany(m => m.GetCustomAttributes(typeof(OnAttribute), true)
            .Select(a => new { EventName = ((OnAttribute)a).EventName, MethodInfo = m }))
    .GroupBy(g => g.EventName, g => g.MethodInfo);

In this solution, the collection is first flattened to get an intermediate collection of anonymous types containing EventName and the MethodInfo. This intermediate collection of anonymous types if then Grouped by EventName to get groups of Methods by EventName.

Upvotes: 2

Related Questions