Я использую LINQ для объектов, чтобы суммировать значения в двух объектах и возвращать одну единственную версию объекта с суммированными итогами.
Проблема, с которой я сталкиваюсь, заключается в том, что функция суммы LINQ суммирует NULL как ноль (0). Я ожидал бы, что если бы у меня было значение "15", а другое значение "нуль", то сумма должна быть "15". Но я ожидал бы, что если первое значение было "null", а второе значение было "null", то сумма также должна быть "null". Однако мне говорят, что сумма равна "0".
Как я могу заставить его функционировать так, как я ожидал бы? Я хочу, чтобы он вел себя, возвращая значение, если есть хотя бы одно значение или возвращает "null", если нет значений.
Теперь для некоторого кода:
virtual public IStatSplit Totals
{
get
{
var cSplit = _splits.Where(s => s.Split == SplitType.COMBINED).SingleOrDefault();
if( cSplit != null )
{ return cSplit; }
cSplit = _splits.Where( s => s.Split != SplitType.COMBINED )
.GroupBy( g => 1 == 1 ).Select( x => new StatSplit
{
AB = (uint?)x.Sum( q => q.AB ),
CI = (uint?)x.Sum( q => q.CI ),
B2 = (uint?)x.Sum( q => q.B2 ),
B3 = (uint?)x.Sum( q => q.B3 ),
GDP = (uint?)x.Sum( q => q.GDP ),
H = (uint?)x.Sum( q => q.H ),
HB = (uint?)x.Sum( q => q.HB ),
HR = (uint?)x.Sum( q => q.HR ),
RBI = (uint?)x.Sum( q => q.RBI ),
IBB = (uint?)x.Sum( q => q.IBB ),
SF = (uint?)x.Sum( q => q.SF ),
SH = (uint?)x.Sum( q => q.SH ),
SO = (uint?)x.Sum( q => q.SO ),
BB = (uint?)x.Sum( q => q.BB ),
Split = SplitType.COMBINED
} ).SingleOrDefault();
return cSplit;
}
}
И вот тестовые данные, которые не пройдут единичный тест:
[TestMethod]
public void PitchingTotals()
{
var splits = GetSplits();
var pitching = new Base.Pitching();
pitching.Splits = splits;
var expected = GetTotalSplit();
var result = pitching.Totals;
// result.RBI = 0
// expected.RBI = null
// this fails because the "0" is not expected
Assert.AreEqual( expected, result );
}
private List<IStatSplit> GetSplits()
{
var lhSplit = new Base.StatSplit
{
AB = 442,
H = 97,
B2 = 14,
B3 = 0,
HR = 6,
BB = 28,
HB = 6,
SF = 1,
SH = 5,
SO = 73,
GDP = 7,
IBB = 4,
CI = 0,
RBI = null,
Split = Enumerations.SplitType.VS_LEFT
};
var rhSplit = new Base.StatSplit
{
AB = 633,
H = 101,
B2 = 9,
B3 = 0,
HR = 5,
BB = 34,
HB = 1,
SF = 1,
SH = 10,
SO = 195,
GDP = 11,
IBB = 2,
CI = 0,
RBI = null,
Split = Enumerations.SplitType.VS_RIGHT
};
List<IStatSplit> splits = new List<IStatSplit>();
splits.Add( lhSplit );
splits.Add( rhSplit );
return splits;
}
private IStatSplit GetTotalSplit()
{
var split = new Base.StatSplit
{
AB = 1075,
H = 198,
B2 = 23,
B3 = 0,
HR = 11,
BB = 62,
HB = 7,
SF = 2,
SH = 15,
SO = 268,
GDP = 18,
IBB = 6,
CI = 0,
RBI = null,
Split = Enumerations.SplitType.COMBINED
};
return split;
}
Вместо Sum
вы можете использовать Aggregate
x.Aggregate(
(uint?)null,
(sum, currentItem) =>
!sum.HasValue && !currentItem.AB.HasValue ?
(uint?)null :
sum.GetValueOrDefault() + currentItem.AB.GetValueOrDefault());
Это начнется с null
uint?
значения и итерации по каждому элементу. Если текущая sum
и значение currentItem.AB
равны null
тогда следующая sum
будет оставаться null
. Если какой- либо не null
, то они добавляются и значение по умолчанию используются, если один является null
, что для uint
является 0.
Основываясь на реакции Юхарра, я сначала попробовал это:
cSplit = _splits.Where( s => s.Split != SplitType.COMBINED )
.GroupBy( g => 1 == 1 ).Select( x => new StatSplit
{
AB = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.AB.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.AB.GetValueOrDefault() ),
CI = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.CI.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.CI.GetValueOrDefault() ),
B2 = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.B2.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.B2.GetValueOrDefault() ),
B3 = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.B3.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.B3.GetValueOrDefault() ),
GDP = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.GDP.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.GDP.GetValueOrDefault() ),
H = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.H.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.H.GetValueOrDefault() ),
HB = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.HB.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.HB.GetValueOrDefault() ),
HR = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.HR.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.HR.GetValueOrDefault() ),
RBI = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.RBI.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.RBI.GetValueOrDefault() ),
IBB = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.IBB.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.IBB.GetValueOrDefault() ),
SF = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.SF.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.SF.GetValueOrDefault() ),
SH = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.SH.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.SH.GetValueOrDefault() ),
SO = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.SO.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.SO.GetValueOrDefault() ),
BB = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.BB.HasValue ?
(uint?)null : sum.GetValueOrDefault() + item.BB.GetValueOrDefault() ),
Split = SplitType.COMBINED
} ).SingleOrDefault();
Это сработало, но это выглядело уродливо для меня. Я немного переделал и придумал следующее:
cSplit = _splits.Aggregate( new StatSplit() { Split = SplitType.COMBINED },
( sum, item ) =>
{
sum.AB = !sum.AB.HasValue && !item.AB.HasValue ? (uint?)null : sum.AB.GetValueOrDefault() + item.AB.GetValueOrDefault();
sum.CI = !sum.CI.HasValue && !item.CI.HasValue ? (uint?)null : sum.CI.GetValueOrDefault() + item.CI.GetValueOrDefault();
sum.B2 = !sum.B2.HasValue && !item.B2.HasValue ? (uint?)null : sum.B2.GetValueOrDefault() + item.B2.GetValueOrDefault();
sum.B3 = !sum.B3.HasValue && !item.B3.HasValue ? (uint?)null : sum.B3.GetValueOrDefault() + item.B3.GetValueOrDefault();
sum.GDP = !sum.GDP.HasValue && !item.GDP.HasValue ? (uint?)null : sum.GDP.GetValueOrDefault() + item.GDP.GetValueOrDefault();
sum.H = !sum.H.HasValue && !item.H.HasValue ? (uint?)null : sum.H.GetValueOrDefault() + item.H.GetValueOrDefault();
sum.HB = !sum.HB.HasValue && !item.HB.HasValue ? (uint?)null : sum.HB.GetValueOrDefault() + item.HB.GetValueOrDefault();
sum.HR = !sum.HR.HasValue && !item.HR.HasValue ? (uint?)null : sum.HR.GetValueOrDefault() + item.HR.GetValueOrDefault();
sum.RBI = !sum.RBI.HasValue && !item.RBI.HasValue ? (uint?)null : sum.RBI.GetValueOrDefault() + item.RBI.GetValueOrDefault();
sum.IBB = !sum.IBB.HasValue && !item.IBB.HasValue ? (uint?)null : sum.IBB.GetValueOrDefault() + item.IBB.GetValueOrDefault();
sum.SF = !sum.SF.HasValue && !item.SF.HasValue ? (uint?)null : sum.SF.GetValueOrDefault() + item.SF.GetValueOrDefault();
sum.SH = !sum.SH.HasValue && !item.SH.HasValue ? (uint?)null : sum.SH.GetValueOrDefault() + item.SH.GetValueOrDefault();
sum.SO = !sum.SO.HasValue && !item.SO.HasValue ? (uint?)null : sum.SO.GetValueOrDefault() + item.SO.GetValueOrDefault();
sum.BB = !sum.BB.HasValue && !item.BB.HasValue ? (uint?)null : sum.BB.GetValueOrDefault() + item.BB.GetValueOrDefault();
return sum;
} );
Первый из них занял около 12 мс, в списке всего два элемента. Утонченный второй забег примерно в 9 мс, и в списке всего два элемента. Я перехожу со второй записью, потому что она более эффективна, а также выглядит более чистым и легче следовать. Спасибо Юхарру за толкание в правильном направлении!
Вы должны написать собственный метод расширения SumOrNull, который может вернуть ваш "uint?". в описанной вами ситуации (null + null = null). Существующий метод Linq Sum возвращает только число, которое не является нулевым.