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

本文关键字:结果 查询 LINQ 获取 null | 更新日期: 2023-09-27 18:27:07

最近我有一项任务要从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检查,其次——这是执行任务的正确方式,还是我没有得到异常的幸运情况?

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

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

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

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

正如Cameron所写,您的第一个FirstOrDefault可能返回null并在链中引入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
}

或者聚合方法:

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;
}