如何避免LINQ查询中的重复代码

k3bvogb1  于 2023-07-31  发布在  其他
关注(0)|答案(4)|浏览(140)

我有一个代码,可以被认为是重复的:

  1. private IEnumerable<decimal?> GetWidth(IEnumerable<Rectangle> rectangles) =>
  2. rectangles.Where(s => s.Width != null)
  3. .Select(s => s.Width);
  4. private IEnumerable<decimal?> GetHeight(IEnumerable<Rectangle> rectangles) =>
  5. rectangles.Where(s => s.Height != null)
  6. .Select(s => s.Height);
  7. private IEnumerable<decimal?> GetDiameter(IEnumerable<Rectangle> rectangles) =>
  8. rectangles.Where(s => s.Diameter != null)
  9. .Select(s => s.Diameter);
  10. private IEnumerable<decimal?> GetWeight(IEnumerable<Rectangle> rectangles) =>
  11. rectangles.Where(s => s.Weight != null)
  12. .Select(s => s.Weight);

字符串
这些方法之间的唯一区别只是字段名称,如Width, Height, Diameter, Weight。有没有可能用一个字符串属性名替换这些名称,并在不使用任何第三方库的情况下只创建一个方法?

ifmq2ha2

ifmq2ha21#

您可以为此创建简单的扩展:

  1. public static class EnumerableExtensions
  2. {
  3. public static IEnumerable<TValue> ExtractValues<TEntity, TValue>(
  4. this IEnumerable<TEntity> items,
  5. Func<TEntity, TValue?> selector)
  6. {
  7. return items.Where(i => selector(i) != null)
  8. .Select(i => selector(i)!);
  9. }
  10. }

字符串
然后这样使用:

  1. var heights = items.ExtractValues(i => i.Height);
  2. var diameters = items.ExtractValues(i => i.Diameter);

展开查看全部
k10s72fa

k10s72fa2#

因为你使用IEnumerable,参数只是选择和检查值,你可以 * 反转 * 操作来选择值并过滤空值。您仍然在执行相同的简单操作-检查所有值,但只允许非空值:

  1. var heights=items.Select(s =>s.Height).Where(x=>x!=null);
  2. var diameters=items.Select(s =>s.Diameter).Where(x=>x!=null);
  3. var weights=items.Select(s =>s.Weight).Where(x=>x!=null);

字符串
您可以将其转换为表达式方法:

  1. IEnumerable<TResult> SelectNonNull<TSource,TResult>(
  2. this IEnumerable<TSource> source,
  3. Func<TSource,TResult?> selector)
  4. {
  5. return source.Select(selector).Where(x=>x!=null);
  6. }
  7. ...
  8. var heights=items.SelectNotNull(s =>s.Height);
  9. var diameters=items.SelectNotNull(s =>s.Diameter);
  10. var weights=items.SelectNotNull(s =>s.Weight);

展开查看全部
oknwwptz

oknwwptz3#

在这个特定的例子中,我将引入一个单独的方法来执行null检查。类似于:

  1. public static T NotNull<T>(this IEnumerable<T?> list) where T : struct
  2. => list.Where(t => t.HasValue).Select( t => t.Value);
  3. private IEnumerable<decimal> GetWeight(IEnumerable<Rectangle> rectangles) =>
  4. rectangles.Select(s => s.Weight).NotNull();

字符串

368yc8dk

368yc8dk4#

如果你真的想用一个字符串来指示要获取的属性,你可以构建一个动态选择作为扩展方法。
此外,通过将Where().Select()切换到Select().Where(),您也只需要一个动态表达式。即

  1. rectangles
  2. .Where(s => s.Width != null)
  3. .Select(s => s.Width);
  4. // becomes
  5. rectangles
  6. .Select(s => s.Width)
  7. .Where(s => s != null);

字符串
那么,如何构建动态过滤器呢?
你可以这样做--基于我对类似问题的回答

  1. public static class ExtensionMethods
  2. {
  3. public static IEnumerable<decimal?> GetProperty(
  4. this IEnumerable<Rectangle> rectangles,
  5. string property)
  6. {
  7. var queryableRectangles = rectangles.AsQueryable();
  8. // w =>
  9. var param = Expression.Parameter(typeof(Rectangle), "w");
  10. var propertyInfo = typeof(Rectangle).GetProperty(property);
  11. if (propertyInfo == null)
  12. throw new Exception($@"Property ""{property}"" was not found");
  13. // w.[property]
  14. var selector = Expression.Property(param, propertyInfo);
  15. // Bring it all together
  16. // Select(w => w.[property])
  17. var selectExpression = Expression.Call(
  18. typeof(Queryable),
  19. nameof(System.Linq.Enumerable.Select),
  20. new Type[] { typeof(Rectangle), typeof(decimal?) },
  21. queryableRectangles.Expression,
  22. Expression.Lambda<Func<Rectangle, decimal?>>(selector, new ParameterExpression[] { param })
  23. );
  24. // Query the collection
  25. var filteredItems = queryableRectangles.Provider.CreateQuery<decimal?>(selectExpression);
  26. // return the list of values removing null values.
  27. return filteredItems
  28. .Where(x => x.HasValue) // Use HasValue instead of != null for Nullable<t>
  29. .ToList();
  30. }
  31. }


然后可以在代码中使用它

  1. var heights = rectangles.GetProperty("Height");

UPDATE -完全通用版本

要使这个完全通用是相当容易的Rectangle with T and十进制替换?with TOut, of course you need to update the method signature a little with the generic type parameters to <T,TOut>`

注意从.Where(x => x.HasValue)变回`。其中(x => x!= null)

  1. public static IEnumerable<TOut> GetProperty<T, TOut>(this IEnumerable<T> source, string property)
  2. {
  3. var queryable = source.AsQueryable();
  4. // w =>
  5. var param = Expression.Parameter(typeof(T), "w");
  6. var propertyInfo = typeof(T).GetProperty(property);
  7. if (propertyInfo == null)
  8. throw new Exception($@"Property ""{property}"" was not found");
  9. // w.[property]
  10. var selector = Expression.Property(param, propertyInfo);
  11. // Bring it all together
  12. // Select(w => w.[property])
  13. var selectExpression = Expression.Call(
  14. typeof(Queryable),
  15. nameof(System.Linq.Enumerable.Select),
  16. new Type[] { typeof(T), typeof(TOut) },
  17. queryable.Expression,
  18. Expression.Lambda<Func<T, TOut>>(selector, new ParameterExpression[] { param })
  19. );
  20. // Run query against the database
  21. var filteredItems = queryable.Provider.CreateQuery<TOut>(selectExpression);
  22. return filteredItems
  23. .Where(x => x != null) // revert to != null for non Nullable<T> return types
  24. .ToList();
  25. }


这也需要稍微调整它的调用方式:

  1. var heights = rectangles.GetProperty<Rectangle, decimal?>("Height");

展开查看全部

相关问题