How to extract distinct elements from list?

class Seal
{
  string Name;
  int Val;
}

List<Seal> Seals;

How to get only distinct Name elements?

For example, if there are seals instances like

seala = {Name = name1, Val = 3}
sealb = {Name = name2, Val = 4}
sealc = {Name = name1, Val = 5}
seald = {Name = name2, Val = 5}

result list should be

ResultList = {Name = name1, Val = 3}, {Name = name2, Val = 4}

How to?

I tried

List<Seal> baseList = new List<Seal>();
List<Seal> realRet = new List<Seal>();
        foreach (var item in Slots)
        {
            if (item.ThisSeal == null || item.ThisSeal.Name == "" || item.ThisSeal.SealCondition == null)
                continue;
            baseList.Add(item.ThisSeal);
        }
realRet = (List<Seal>)baseList.GroupBy(x => x.Name).Select(g => g.First());

but error says

InvalidCastException: Specified cast is not valid.

I see you’re already using linq here, and with linq you have the ‘Distinct’ method:

One of its overloads takes in an IEqualityComparer (unfortunately it doesn’t take in a Comparison delegate, but so it goes).

So I created a simple IEqualityComparer that compares the names:

    class SealNameEqualityComparer : IEqualityComparer<Seal>
    {
        public static readonly SealNameEqualityComparer Default = new SealNameEqualityComparer();

        public bool Equals(Seal x, Seal y)
        {
            return x.Name == y.Name;
        }

        public int GetHashCode(Seal obj)
        {
            return obj.Name != null ? obj.Name.GetHashCode() : 0;
        }
    }

And here you go:

var realRet = baseList.Distinct(SealNameEqualityComparer.Default).ToList();

Note, you could do this all in linq like so:

var realRet Slots.Where(item => item.ThisSeal != null && !string.IsNullOrEmpty(this.SealName.Name) && item.ThisSeal.SealCondition != null)
    .Select(item => item.ThisSeal)
    .Distinct(SealNameEqualityComparer.Default)
    .ToList();

…

In an aside, the reason you’re getting that exception is that the ‘Select’ linq method doesn’t return a List, it returns an IEnumerable:

You can’t cast that to List, because it’s not a List. Rather it’s an object that when enumerated performs the ‘Select’.

Use ‘ToList’ to take the result of that enumeration and stick it in a List:

But yeah… back at my example, I removed the need for GroupBy Select First.

You can continue using that. But it requires more objects to be created than Distinct, and will perform slower (though on small lists it won’t matter).

Go here to the Enumerable source code to see the complexity differences:
https://referencesource.microsoft.com/#system.core/System/Linq/Enumerable.cs

Distinct just uses a ‘HashSet’ (which explains why it only takes an IEqualityComparer, it feeds that to the HashSet), and then uses that set to generate a Distinct list since HashSets can’t have duplicates in them and have O(1) lookup.

Where as GroupBy does… a lot… it generates a “GroupedEnumerable” object, that generates a “Lookup” object which creates an internal array to store the groupings, and performs quite a bit of logic.

1 Like

@lordofduct Perfect!!

This is final code that works.

using static ExtensionClass;

public virtual List<Seal> GetDifferSeals()
    {       // from equipped seals, get only different names
        List<Seal> baseList = new List<Seal>(); 
        var realRet = Slots.Where(item => item.ThisSeal != null && !string.IsNullOrEmpty(item.ThisSeal.Name)
            && item.ThisSeal.SealCondition != null)
            .Select(item => item.ThisSeal)
            .Distinct(SealNameEqualityComparer.Default)
            .ToList();
        Debug.Log("This is equipment, " + Name + ", baseList count is " + baseList.Count + " realRet count is " + realRet.Count());
        return realRet;
    }