即使我有匹配的结果,从LINQ查询获取null

时间:2014-10-20 16:22:28

标签: c# linq

最近,我有一项任务是从List<Account>选择一个帐户,条件是我有一个货币列表 - List<string> currencies = new List<string> { "USD", "GBP", "EUR" };,结果应该是第一个货币为USD的帐户,如果不是这样的帐户,那么currency == GBP帐户和EUR中的任何帐户。

因此,为了重新创建问题,我创建了一个简单的类Account

class Account
{
    public string Currency { get; set; }
    public string IBAN { get; set; }
    public decimal Amount { get; set; }
}

然后我创建了两个实例:

列出帐户=新列表();

        Account account = new Account();

        account.Amount = 1;
        account.Currency = "USD";
        account.IBAN = "A1";

        accounts.Add(account);

        Account account1 = new Account();

        account1.Amount = 2;
        account1.Currency = "EUR";
        account1.IBAN = "A2";

        accounts.Add(account1);

然后我执行了这个查询:

var acc = currencies.Select(currency => accounts.FirstOrDefault(x => x.Currency == currency))
     .Where(a => a.Amount > 1)
     .FirstOrDefault();

最终导致意外(对我而言)结果 - 错误说:

An unhandled exception of type 'System.NullReferenceException' occurred.. Additional information: Object reference not set to an instance of an object.

尽管有货币订单,我确实需要一个额外的条件,所以我真的很困惑为什么我得到这个结果。如果我改变它:

var acc = currencies.Select(currency => accounts.FirstOrDefault(x => x.Currency == currency))
     .Where(a => a != null && a.Amount > 1)
     .FirstOrDefault();

然后我似乎得到了正确的行为,但首先我真的想知道为什么我必须进行此null检查,然后再次 - 这是执行任务的正确方法还是仅仅是幸运的情况下,我没有例外?

2 个答案:

答案 0 :(得分:4)

accounts.FirstOrDefault()"GBP"货币返回null。

然后在a.Amount中使用此空值进行Where()。我想你想要:

currencies.Select(currency => accounts.FirstOrDefault(x => x.Currency == currency))
    .Where(a => a != null && a.Amount > 1)   // <-- Added null check
    .FirstOrDefault();

答案 1 :(得分:1)

正如Cameron所写,你的第一个FirstOrDefault可以返回null并在链中引入一个空引用异常。

更易读的方法可能是使用字典:

string selectedCurrency = 
  currencies.FirstOrDefault(currency => accountsByCurrency.ContainsKey(currency));
if (selectedCurrency != null)
{
  Account selectedAccount = accountsByCurrency[selectedCurrency];

  // Do something with selected account
}
else
{
  // No account was found
}

Aggregate方法:

var currencyRanks = new Dictionary<string, int>()
{
  { "USD", 1 },
  { "GBP", 2 },
  { "EUR", 3 }
};

int? workingRank = null;
Account selectedAccount = accounts.Aggregate((working, current) =>
{
  int currentRank = 0;
  if (currencyRanks.TryGetValue(current.Currency, out currentRank) && 
     (currentRank < workingRank))
  {
    workingRank = currentRank;
    return current;
  }

  return working;
};

请注意,一旦找到“美元”帐户,这些都不允许部分迭代收集并纾困。

如果你的收藏很大并且你也想要这种行为,旧的时尚方式仍然可读:

private Account SelectAccountByCurrency(IEnumerable<Account> accounts)
{
  Account result = null;
  int? workingRank = null;

  foreach (Account account in accounts)
  {
    int currentRank = 0;
    if (currencyRanks.TryGetValue(account.Currency, out currentRank)) 
    {        
      if (currentRank == 1)
      {
        return account;
      }
      else if (currentRank < workingRank)
      {
        result = account;
        workingRank = currentRank;
      }
    }

    return result;
}