views:

49

answers:

3

I am aggregating multiple List's into one List and would like to make it distinct based on one of the properties of Foo (Foo.Prop1).. I do not have access to modify Foo's Equality comparer.

Dictionary<string, List<Foo>> fooDictionary = new Dictionary<string, List<Foo>>();
List<Foo> foovals = (from e in fooDictionary
                     where e.Key == "foo1" || e.Key == "foo2" || e.Key == "foo3"
                     select e.Value).SelectMany(f => f).ToList();

the only thing missing here is the .Distinct() at the end, to make Foo's unique, however, in my case, i can't modify anything about Foo, so simply calling Distinct() will not work.

Is there a way to modify this query to return items Distinct based on Foo.Prop1 ?

+3  A: 

You can pass a custom equality comparer to Distinct():

.SelectMany().Distinct(new FooEqualityComparer());

In a new file, "FooEqualityComparer.cs":

public class FooEqualityComparer : IEqualityComparer<Foo>
{
    public bool Equals(Foo x, Foo y)
    {
        return Equals(x.Prop1, y.Prop1);
    }

    public int GetHashCode(Foo x)
    {
        return x.Prop1.GetHashCode();
    }
}
codekaizen
also, can't really do that.. this is in a codesmith template, and Foo is generated dynamically by xml schema, so not even accessible in a code template.. at least that i know how..
Sonic Soul
You don't need to access the class code in order to write an equality comparer...
codekaizen
I do, because Foo is a custom class, and equality comparer will need to be aware of what properties it has to compare them
Sonic Soul
But... you write the comparer, so you enter this knowledge into the class. How do you choose to base equality on Prop1? That decision goes into the comparer.
codekaizen
the problem is that Foo exists only within my main template based on xml schema loaded by one of the properties.. there is no class definition anywehre.. to write a new class i would need to put it in a separate code template. and there it would not know that Foo has a Prop1 unless i used reflection..
Sonic Soul
Then how do you even know you want to base equality on it?
codekaizen
i have access to the properties within the template.. currently i do it by looping over all values, and keeping already added values in a separate list.. if (!namesAlreadyAdded.Contains(myObj.Prop1)) add to main collection
Sonic Soul
so i can interact with the object programmatically, i just can't seem to be able to create new classes within the .cst template.. i can only do that in separate templates and reference them.. but those templates do not have the xml schema generated objects.. and unnecessary headache to make that work.. i can just keep my simple loop which works..
Sonic Soul
Ok, then. The equality compare is _not_ inside your templates. You use it to interact _programmatically_ with the instances. You can create the definition anywhere: it is a separate class.
codekaizen
again, equality compare needs to have knowledge about the class it is comparing. and that is not available outside of main tempalte
Sonic Soul
Again, then how do you know you want to base equality on Prop1? You say you can interact programmatically with it. You even do so with the LINQ query. Just add the equality comparer to this mix.
codekaizen
Then you can't interact with it programmatically to know about Prop1.
codekaizen
@Sonic: I’m with codekaizen on this. If Jon’s answer works for you, then so does this one.
Timwi
Timwi is correct. If you can access Prop1, you can use an equality comparer. I'm sorry you can't see that.
codekaizen
becasause equality comparer requires creating a new class. as far as i can tell that is not possible within (the same main) codesmith template. i mean.. this is a good answer, and i +1'd it.. i just can't do it because of codesmith limitations.
Sonic Soul
i also don't know how to make Jon's solution work in codesmith, but i believe that DisntictBy is ideal answer, because it would work in many places w/out having to write comparers every time.
Sonic Soul
@Sonic Soul - it doesn't need to be in the template. What is your reasoning about why it must be? Where you execute the `Distinct`, that's where you create the class.
codekaizen
see 5th comment :)
Sonic Soul
Well, then that is your answer, too.
codekaizen
A: 
var tempKeys = new HashSet<int>();    // change int to the actual type of Prop1

List<Foo> foovals = (from e in fooDictionary
                     where e.Key == "foo1" || e.Key == "foo2" || e.Key == "foo3"
                     select e.Value).SelectMany(f => f)
                                    .Where(f => tempKeys.Add(f.Prop1))
                                    .ToList();
LukeH
Too “clever” and unreadable. And hacky: `Where` with side effects, yuck.
Timwi
@Timwi: It's just a proof-of-concept, and is pretty much what `Distinct` itself does behind the scenes. Wrap it up in a general-purpose extension method that also accepts a key-selector function and you've just re-written `DistinctBy`, as mentioned in Jon's answer.
LukeH
+1  A: 

You could use the DistinctBy method in MoreLINQ:

var query = fooVals.DistinctBy(foo => foo.Property1);
Jon Skeet
nice! although, this is inside of a CodeSmith template, so not sure if i can add language extensions..
Sonic Soul