Respostas:
Você está tentando se diferenciar em mais de um campo? Nesse caso, basta usar um tipo anônimo e o operador Distinct e tudo ficará bem:
var query = doc.Elements("whatever")
.Select(element => new {
id = (int) element.Attribute("id"),
category = (int) element.Attribute("cat") })
.Distinct();
Se você está tentando obter um conjunto distinto de valores de um tipo "maior", mas apenas olhando para um subconjunto de propriedades para o aspecto distintivo, provavelmente deseja DistinctBy
implementá-lo no MoreLINQ em DistinctBy.cs
:
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
foreach (TSource element in source)
{
if (knownKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
(Se você passar null
como comparador, ele usará o comparador padrão para o tipo de chave.)
Basta usar o Distinct()
com seu próprio comparador.
Além da resposta de Jon Skeet, você também pode usar o grupo por expressões para obter os grupos únicos com uma contagem para as iterações de cada grupo:
var query = from e in doc.Elements("whatever")
group e by new { id = e.Key, val = e.Value } into g
select new { id = g.Key.id, val = g.Key.val, count = g.Count() };
Para quem ainda procura; aqui está outra maneira de implementar um comparador lambda personalizado.
public class LambdaComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _expression;
public LambdaComparer(Func<T, T, bool> lambda)
{
_expression = lambda;
}
public bool Equals(T x, T y)
{
return _expression(x, y);
}
public int GetHashCode(T obj)
{
/*
If you just return 0 for the hash the Equals comparer will kick in.
The underlying evaluation checks the hash and then short circuits the evaluation if it is false.
Otherwise, it checks the Equals. If you force the hash to be true (by assuming 0 for both objects),
you will always fall through to the Equals check which is what we are always going for.
*/
return 0;
}
}
você pode criar uma extensão para o linq Distinct que pode receber os lambda
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, T, bool> lambda)
{
return list.Distinct(new LambdaComparer<T>(lambda));
}
Uso:
var availableItems = list.Distinct((p, p1) => p.Id== p1.Id);
Estou um pouco atrasado para a resposta, mas você pode fazer isso se quiser o elemento inteiro, não apenas os valores pelos quais deseja agrupar:
var query = doc.Elements("whatever")
.GroupBy(element => new {
id = (int) element.Attribute("id"),
category = (int) element.Attribute("cat") })
.Select(e => e.First());
Isso fornecerá o primeiro elemento inteiro correspondente ao seu grupo por seleção, como o segundo exemplo de Jon Skeets usando DistinctBy, mas sem implementar o comparador IEqualityComparer. O DistinctBy provavelmente será mais rápido, mas a solução acima envolverá menos código se o desempenho não for um problema.
// First Get DataTable as dt
// DataRowComparer Compare columns numbers in each row & data in each row
IEnumerable<DataRow> Distinct = dt.AsEnumerable().Distinct(DataRowComparer.Default);
foreach (DataRow row in Distinct)
{
Console.WriteLine("{0,-15} {1,-15}",
row.Field<int>(0),
row.Field<string>(1));
}
Como estamos falando de ter todos os elementos exatamente uma vez, um "conjunto" faz mais sentido para mim.
Exemplo com classes e IEqualityComparer implementados:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public Product(int x, string y)
{
Id = x;
Name = y;
}
}
public class ProductCompare : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y)
{ //Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.Id == y.Id && x.Name == y.Name;
}
public int GetHashCode(Product product)
{
//Check whether the object is null
if (Object.ReferenceEquals(product, null)) return 0;
//Get hash code for the Name field if it is not null.
int hashProductName = product.Name == null ? 0 : product.Name.GetHashCode();
//Get hash code for the Code field.
int hashProductCode = product.Id.GetHashCode();
//Calculate the hash code for the product.
return hashProductName ^ hashProductCode;
}
}
Agora
List<Product> originalList = new List<Product> {new Product(1, "ad"), new Product(1, "ad")};
var setList = new HashSet<Product>(originalList, new ProductCompare()).ToList();
setList
terá elementos únicos
Pensei nisso ao lidar com o .Except()
que retorna uma diferença de set