with tutorial expresiones c# linq .net-3.5 lambda

c# - tutorial - Cómo obtener una instancia distinta de una lista de Lambda o LINQ



linq with lambda expression in c# (7)

Tengo una clase como esta:

class MyClass<T> { public string value1 { get; set; } public T objT { get; set; } }

y una lista de esta clase. Me gustaría utilizar .net 3.5 lambda o linq para obtener una lista de MyClass por valor distinto1. Supongo que esto es posible y mucho más simple que en .net 2.0 para almacenar en caché una lista como esta:

List<MyClass<T>> list; ... List<MyClass<T>> listDistinct = new List<MyClass<T>>(); foreach (MyClass<T> instance in list) { // some code to check if listDistinct does contain obj with intance.Value1 // then listDistinct.Add(instance); }

¿Cuál es la forma de lambda o LINQ para hacerlo?


Echa un vistazo a Enumerable.Distinct() , que puede aceptar un IEqualityComparer:

class MyClassComparer<T> : IEqualityComparer<MyClass<T>> { // Products are equal if their names and product numbers are equal. public bool Equals(MyClass<T> x, MyClass<T>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.value1 == y.value1; } // If Equals() returns true for a pair of objects, // GetHashCode must return the same value for these objects. public int GetHashCode(MyClass<T> x) { // Check whether the object is null. if (Object.ReferenceEquals(x, null)) return 0; // Get the hash code for the Name field if it is not null. return (x.value1 ?? "").GetHashCode(); } }

Su fragmento de código podría verse así:

List<MyClass<T>> list; ... List<MyClass<T>> listDistinct = list.Distinct(new MyClassComparer<T>).ToList();


En linq esto es más adelantado al grupo

list.GroupBy(li => li.value, (key, grp) => li.FirstOrDefault());


Esto será más simple ...

var distinctList = list.GroupBy(l => l.value1, (key, c) => l.FirstOrDefault());


Hmm ... Probablemente escribiría un IEqualityComparer<T> personalizado IEqualityComparer<T> para que pueda usar:

var listDistinct = list.Distinct(comparer).ToList();

y escribe el comparador a través de LINQ ...

Posiblemente un poco exagerado, pero reutilizable, al menos:

Uso primero:

static class Program { static void Main() { var data = new[] { new { Foo = 1,Bar = "a"}, new { Foo = 2,Bar = "b"}, new {Foo = 1, Bar = "c"} }; foreach (var item in data.DistinctBy(x => x.Foo)) Console.WriteLine(item.Bar); } } }

Con los métodos de utilidad:

public static class ProjectionComparer { public static IEnumerable<TSource> DistinctBy<TSource,TValue>( this IEnumerable<TSource> source, Func<TSource, TValue> selector) { var comparer = ProjectionComparer<TSource>.CompareBy<TValue>( selector, EqualityComparer<TValue>.Default); return new HashSet<TSource>(source, comparer); } } public static class ProjectionComparer<TSource> { public static IEqualityComparer<TSource> CompareBy<TValue>( Func<TSource, TValue> selector) { return CompareBy<TValue>(selector, EqualityComparer<TValue>.Default); } public static IEqualityComparer<TSource> CompareBy<TValue>( Func<TSource, TValue> selector, IEqualityComparer<TValue> comparer) { return new ComparerImpl<TValue>(selector, comparer); } sealed class ComparerImpl<TValue> : IEqualityComparer<TSource> { private readonly Func<TSource, TValue> selector; private readonly IEqualityComparer<TValue> comparer; public ComparerImpl( Func<TSource, TValue> selector, IEqualityComparer<TValue> comparer) { if (selector == null) throw new ArgumentNullException("selector"); if (comparer == null) throw new ArgumentNullException("comparer"); this.selector = selector; this.comparer = comparer; } bool IEqualityComparer<TSource>.Equals(TSource x, TSource y) { if (x == null && y == null) return true; if (x == null || y == null) return false; return comparer.Equals(selector(x), selector(y)); } int IEqualityComparer<TSource>.GetHashCode(TSource obj) { return obj == null ? 0 : comparer.GetHashCode(selector(obj)); } } }


Las respuestas de Marc y Dahlbyk parecen funcionar muy bien. Aunque tengo una solución mucho más simple. En lugar de usar Distinct , puede usar GroupBy . Dice así:

var listDistinct = list.GroupBy( i => i.value1, (key, group) => group.First() ).ToArray();

Tenga en cuenta que he pasado dos funciones al GroupBy() . El primero es un selector de llave. El segundo solo obtiene un elemento de cada grupo. De su pregunta, asumí que First() era la correcta. Puede escribir uno diferente, si lo desea. Puedes probar Last() para ver a qué me refiero.

Ejecuté una prueba con la siguiente entrada:

var list = new [] { new { value1 = "ABC", objT = 0 }, new { value1 = "ABC", objT = 1 }, new { value1 = "123", objT = 2 }, new { value1 = "123", objT = 3 }, new { value1 = "FOO", objT = 4 }, new { value1 = "BAR", objT = 5 }, new { value1 = "BAR", objT = 6 }, new { value1 = "BAR", objT = 7 }, new { value1 = "UGH", objT = 8 }, };

El resultado fue:

//{ value1 = ABC, objT = 0 } //{ value1 = 123, objT = 2 } //{ value1 = FOO, objT = 4 } //{ value1 = BAR, objT = 5 } //{ value1 = UGH, objT = 8 }

No lo he probado para el funcionamiento. Creo que esta solución es probablemente un poco más lenta que una que usa Distinct . A pesar de esta desventaja, hay dos grandes ventajas: simplicidad y flexibilidad. Generalmente, es mejor favorecer la simplicidad que la optimización, pero realmente depende del problema que intentas resolver.


Puedes usar este método de extensión:

IEnumerable<MyClass> distinctList = sourceList.DistinctBy(x => x.value1); public static IEnumerable<TSource> DistinctBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) { var knownKeys = new HashSet<TKey>(); return source.Where(element => knownKeys.Add(keySelector(element))); }


Tomé la respuesta de Marc, la arreglé para trabajar con TSource como un tipo de valor (prueba de defecto (TSource) en lugar de nulo), limpié algunas especificaciones de tipo redundante y escribí algunas pruebas para ello. Esto es lo que estoy usando hoy. Gracias Marc por la gran idea y la implementación.

public static class LINQExtensions { public static IEnumerable<TSource> DistinctBy<TSource, TValue>( this IEnumerable<TSource> source, Func<TSource, TValue> selector) { var comparer = ProjectionComparer<TSource>.CompareBy( selector, EqualityComparer<TValue>.Default); return new HashSet<TSource>(source, comparer); } } public static class ProjectionComparer<TSource> { public static IEqualityComparer<TSource> CompareBy<TValue>( Func<TSource, TValue> selector) { return CompareBy(selector, EqualityComparer<TValue>.Default); } public static IEqualityComparer<TSource> CompareBy<TValue>( Func<TSource, TValue> selector, IEqualityComparer<TValue> comparer) { return new ComparerImpl<TValue>(selector, comparer); } sealed class ComparerImpl<TValue> : IEqualityComparer<TSource> { private readonly Func<TSource, TValue> _selector; private readonly IEqualityComparer<TValue> _comparer; public ComparerImpl( Func<TSource, TValue> selector, IEqualityComparer<TValue> comparer) { if (selector == null) throw new ArgumentNullException("selector"); if (comparer == null) throw new ArgumentNullException("comparer"); _selector = selector; _comparer = comparer; } bool IEqualityComparer<TSource>.Equals(TSource x, TSource y) { if (x.Equals(default(TSource)) && y.Equals(default(TSource))) { return true; } if (x.Equals(default(TSource)) || y.Equals(default(TSource))) { return false; } return _comparer.Equals(_selector(x), _selector(y)); } int IEqualityComparer<TSource>.GetHashCode(TSource obj) { return obj.Equals(default(TSource)) ? 0 : _comparer.GetHashCode(_selector(obj)); } } }

Y la clase de prueba:

[TestClass] public class LINQExtensionsTest { [TestMethod] public void DistinctByTestDate() { var list = Enumerable.Range(0, 200).Select(i => new { Index = i, Date = DateTime.Today.AddDays(i%4) }).ToList(); var distinctList = list.DistinctBy(l => l.Date).ToList(); Assert.AreEqual(4, distinctList.Count); Assert.AreEqual(0, distinctList[0].Index); Assert.AreEqual(1, distinctList[1].Index); Assert.AreEqual(2, distinctList[2].Index); Assert.AreEqual(3, distinctList[3].Index); Assert.AreEqual(DateTime.Today, distinctList[0].Date); Assert.AreEqual(DateTime.Today.AddDays(1), distinctList[1].Date); Assert.AreEqual(DateTime.Today.AddDays(2), distinctList[2].Date); Assert.AreEqual(DateTime.Today.AddDays(3), distinctList[3].Date); Assert.AreEqual(200, list.Count); } [TestMethod] public void DistinctByTestInt() { var list = Enumerable.Range(0, 200).Select(i => new { Index = i % 4, Date = DateTime.Today.AddDays(i) }).ToList(); var distinctList = list.DistinctBy(l => l.Index).ToList(); Assert.AreEqual(4, distinctList.Count); Assert.AreEqual(0, distinctList[0].Index); Assert.AreEqual(1, distinctList[1].Index); Assert.AreEqual(2, distinctList[2].Index); Assert.AreEqual(3, distinctList[3].Index); Assert.AreEqual(DateTime.Today, distinctList[0].Date); Assert.AreEqual(DateTime.Today.AddDays(1), distinctList[1].Date); Assert.AreEqual(DateTime.Today.AddDays(2), distinctList[2].Date); Assert.AreEqual(DateTime.Today.AddDays(3), distinctList[3].Date); Assert.AreEqual(200, list.Count); } struct EqualityTester { public readonly int Index; public readonly DateTime Date; public EqualityTester(int index, DateTime date) : this() { Index = index; Date = date; } } [TestMethod] public void TestStruct() { var list = Enumerable.Range(0, 200) .Select(i => new EqualityTester(i, DateTime.Today.AddDays(i%4))) .ToList(); var distinctDateList = list.DistinctBy(e => e.Date).ToList(); var distinctIntList = list.DistinctBy(e => e.Index).ToList(); Assert.AreEqual(4, distinctDateList.Count); Assert.AreEqual(200, distinctIntList.Count); } }