C# 选择许多匿名类型并跳过迭代

C# 选择许多匿名类型并跳过迭代,c#,anonymous-types,linq,C#,Anonymous Types,Linq,很长一段时间以来,我一直试图找到一种“干净”的模式来处理。当您不想总是返回结果时,请使用匿名类型选择many。我最常见的用例如下所示: //c is a customer var context = GetContextForCustomer(c); // look up some data, myData using the context connection if (someCondition) return myData.Select(x => new { CustomerID

很长一段时间以来,我一直试图找到一种“干净”的模式来处理
。当您不想总是返回结果时,请使用匿名类型选择many
。我最常见的用例如下所示:

//c is a customer
var context = GetContextForCustomer(c);
// look up some data, myData using the context connection
if (someCondition)
  return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
else
  return null;
customers
  .AsParallel()
  .SelectMany(c => {
     var context = GetContextForCustomer(c);
     if (someCondition)
       return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
     else
       continue?   return null?   return empty list?
  })
  .ToList();
  • 我们有一个客户名单,我想做报告
  • 每个客户的数据都驻留在一个单独的数据库中,因此我并行执行
    。选择many
  • 在每个lambda表达式中,我为客户收集最终报告的结果
  • 如果某个客户应该被跳过,我需要返回一个空列表
  • 为了快速报告,我经常会将这些信息快速整理出来,所以我更喜欢匿名类型
  • 例如,逻辑可能如下所示:

    //c is a customer
    var context = GetContextForCustomer(c);
    // look up some data, myData using the context connection
    if (someCondition)
      return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
    else
      return null;
    
    customers
      .AsParallel()
      .SelectMany(c => {
         var context = GetContextForCustomer(c);
         if (someCondition)
           return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
         else
           continue?   return null?   return empty list?
      })
      .ToList();
    
    这可以作为foreach语句实现:

    var results = new List<WhatType?>();
    foreach (var c in customers) {
      var context = GetContextForCustomer(c);
      if (someCondition)
        results.AddRange(myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 }));
    }
    
    这两种方法都存在问题。
    foreach
    解决方案需要初始化
    列表来存储结果,并且您必须定义类型。使用
    .SelectMany
    .Where
    通常是不切实际的,因为
    someCondition
    的逻辑相当复杂,并且依赖于某些数据查找。因此,我的理想解决方案如下所示:

    //c is a customer
    var context = GetContextForCustomer(c);
    // look up some data, myData using the context connection
    if (someCondition)
      return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
    else
      return null;
    
    customers
      .AsParallel()
      .SelectMany(c => {
         var context = GetContextForCustomer(c);
         if (someCondition)
           return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
         else
           continue?   return null?   return empty list?
      })
      .ToList();
    
    要跳过返回值,我应该在
    else
    行中输入什么?没有一个解决方案是我能想出的,或者是理想的:

  • continue
    未编译,因为它不是活动的
    foreach
    循环
  • 返回null
    导致
    NRE
  • return
    空列表要求我再次初始化匿名类型的列表
  • 有没有一种干净、简单、整洁的方法可以满足我所有的(挑剔的)要求?

    您可以尝试以下方法:

    customers
      .AsParallel()
      .SelectMany(c => {
         var context = GetContextForCustomer(c);
         if (someCondition)
           return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
         else
           return Enumerable.Empty<int>().Select(x => new { CustomerID = 0, X1 = "defValue", X2 = "defValue" });
      })
      .ToList();
    
    客户
    .天冬酰胺()
    .SelectMany(c=>{
    var context=GetContextForCustomer(c);
    如果(某些条件)
    返回myData.Select(x=>new{CustomerID=c,X1=x.X1,X2=x.X2});
    其他的
    返回Enumerable.Empty().Select(x=>new{CustomerID=0,X1=“defValue”,X2=“defValue”});
    })
    .ToList();
    

    编译器将具有相同属性集(相同名称和类型)的所有匿名类型合并到一个匿名类中。这就是为什么您的
    选择
    可枚举上的一个都会返回相同的
    T

    您可以返回一个空的
    可枚举
    。下面是一个示例(虽然没有您的客户和
    someCondition
    ,因为我不知道他们是什么,但与您的示例具有相同的一般形式):


    不知道某个条件和我的数据看起来像什么

    为什么不选择
    其中的
    上下文:

    customers
    .Select(c => GetContextForCustomer(c))
    .Where(ctx => someCondition)
    .SelectMany(ctx => 
        myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
    

    编辑:我刚刚意识到您需要进一步携带
    客户
    上下文
    ,因此您可以这样做:

    customers
    .Select(c => new { Customer = c, Context = GetContextForCustomer(c) })
    .Where(x => someCondition(x.Context))
    .SelectMany(x => 
        myData.Select(d => new { CustomerID = x.Customer, X1 = d.x1, X2 = d.x2 });
    

    您可以创建自己的
    SelectMany
    LINQ方法变量,该方法支持
    null
    s:

    public static class EnumerableExtensions
    {
        public static IEnumerable<TResult> NullableSelectMany<TSource, TResult> (
            this IEnumerable<TSource> source,
            Func<TSource, IEnumerable<TResult>> selector)
        {
            if (source == null) 
                throw new ArgumentNullException("source");
            if (selector == null) 
                throw new ArgumentNullException("selector");
            foreach (TSource item in source) {
                IEnumerable<TResult> results = selector(item);
                if (results != null) {
                    foreach (TResult result in results)
                        yield return result;
                }
            }
        }
    }
    
    公共静态类EnumerableExtensions
    {
    公共静态IEnumerable NullableSelectMany(
    这是一个数不清的来源,
    Func选择器)
    {
    if(source==null)
    抛出新的ArgumentNullException(“源”);
    if(选择器==null)
    抛出新的ArgumentNullException(“选择器”);
    foreach(源中的TSource项){
    IEnumerable结果=选择器(项目);
    如果(结果!=null){
    foreach(TResult result in results)
    收益结果;
    }
    }
    }
    }
    

    现在,您可以在
    选择器
    lambda中返回
    null

    接受的答案返回
    动态
    。最干净的方法是将过滤逻辑移动到
    中,其中
    ,这使得整个事情在linq上下文中看起来更好。既然你在问题中明确排除了这一点,而且我不喜欢在linq通话中用多行文字写代表,我会尝试,但有人会说它更粗糙

    var results = new 
    { 
        customerID = default(int), //notice the casing of property names
        x1 = default(U), //whatever types they are
        x2 = default(V) 
    }.GetEmptyListOfThisType();
    
    foreach (var customerID in customers) {
      var context = GetContextForCustomer(customerID);
      if (someCondition)
        results.AddRange(myData.Select(x => new { customerID, x.x1, x.x2 }));
    }
    
    public static List<T> GetEmptyListOfThisType<T>(this T item)
    {
        return new List<T>();
    }
    
    var结果=新
    { 
    customerID=default(int),//注意属性名的大小写
    x1=默认值(U),//无论它们是什么类型
    x2=默认值(V)
    }.getEmptyListoftHistType();
    foreach(客户中的var客户ID){
    var context=GetContextForCustomer(customerID);
    如果(某些条件)
    AddRange(myData.Select(x=>new{customerID,x.x1,x.x2}));
    }
    公共静态列表GetEmptyListOftHistType(此T项)
    {
    返回新列表();
    }
    

    请注意,属性名称的正确使用与其他变量名称一致,因此您不必在
    Select
    调用中再次写入属性名称。

    这将导致大量动态调用,进而导致反射。就性能而言,这不是最好的选择。很好。但是他也说他只需要这个来生成一些快速报告。我最喜欢这个解决方案,因为它很短,不需要任何定制的扩展方法。我尝试了
    Enumerable.Empty()
    ,但在没有
    的情况下,它无法确定类型。谢谢如果您这样做,您会意识到最终的返回类型也将是
    IEnumerable
    。根据您的用例,这可能会很好地工作。这是一种很好的方法。我考虑的另一件事是
    。选择(…)。其中(x=>x!=null)。选择多个(x=>x)
    。没有那么漂亮,但不需要我在任何地方指定匿名类型。@mellamokb ReSharper实际上向我建议了这一点。:)对于
    IEnumerable
    ,我也有很多扩展方法,所以我会自己编写类似
    .Select(…).WhereNotNull().flatte()
    ,这对我来说已经足够了。我意识到我的示例做得不够好。实际上,
    someCondition
    myData
    背后的逻辑是20-30行代码,并且发展迅速,因此我最喜欢
    中的所有代码。选择many
    。这个解决方案是基本的