Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Extensions.Test/EnumerableExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,4 +456,42 @@

Assert.ThrowsExactly<InvalidOperationException>(() => result.ToList());
}

private sealed class NullToString
{
public override string? ToString() => null;
}

[TestMethod]
[DataRow(NullItemHandling.Remove)]
[DataRow(NullItemHandling.Include)]
[DataRow(NullItemHandling.Throw)]
public void ToStringEnumerableKeepsAnItemWhoseToStringIsNullAsEmpty(NullItemHandling nullItemHandling)
{
object[] items = [new NullToString(), "x"];

List<string?> result = [.. items.ToStringEnumerable(nullItemHandling)];

CollectionAssert.AreEqual(new List<string?> { string.Empty, "x" }, result);

Check warning on line 475 in Extensions.Test/EnumerableExtensionsTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Extensions&issues=AaEfZPaBeqOzavI2CKIe&open=AaEfZPaBeqOzavI2CKIe&pullRequest=163
}

[TestMethod]
public void ToStringEnumerableWithoutHandlingKeepsAnItemWhoseToStringIsNullAsEmpty()
{
object[] items = [new NullToString(), "x"];

List<string> result = [.. items.ToStringEnumerable()];

CollectionAssert.AreEqual(new List<string> { string.Empty, "x" }, result);

Check warning on line 485 in Extensions.Test/EnumerableExtensionsTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Extensions&issues=AaEfZPaBeqOzavI2CKId&open=AaEfZPaBeqOzavI2CKId&pullRequest=163
}

[TestMethod]
[DataRow(NullItemHandling.Remove)]
[DataRow(NullItemHandling.Include)]
public void JoinMatchesToStringEnumerableForTheSameHandling(NullItemHandling nullItemHandling)
{
object?[] items = [new NullToString(), null, "x"];

Assert.AreEqual(string.Join(",", items.ToStringEnumerable(nullItemHandling)), items.Join(",", nullItemHandling));
}
}
16 changes: 9 additions & 7 deletions Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#pragma warning disable KTSU0004 // Use Ensure.NotNull instead of manual null check
if (items is null)
{
throw new ArgumentNullException(nameof(items), "Items cannot be null.");

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.

Check warning on line 33 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'Items cannot be null.' 7 times.
}
#pragma warning restore KTSU0004 // Use Ensure.NotNull instead of manual null check

Expand Down Expand Up @@ -190,7 +190,10 @@
/// <typeparam name="T">The type of the items in the enumerable.</typeparam>
/// <param name="items">The enumerable to convert.</param>
/// <param name="nullItemHandling">Specifies how to handle null items.</param>
/// <returns>An enumerable of strings.</returns>
/// <returns>
/// An enumerable of strings. <paramref name="nullItemHandling"/> applies to null items only; a non-null item whose
/// <see cref="object.ToString"/> returns null is always kept, as an empty string.
/// </returns>
/// <exception cref="InvalidOperationException">Thrown while enumerating the result if <paramref name="nullItemHandling"/> is set to <see cref="NullItemHandling.Throw"/> and the enumerable contains null items.</exception>
public static IEnumerable<string?> ToStringEnumerable<T>(this IEnumerable<T> items, NullItemHandling nullItemHandling)
{
Expand All @@ -203,9 +206,11 @@

// The argument check above stays eager; the null check happens during the one enumeration the
// caller makes, so a single-pass source is not consumed early and a null added later is caught.
// Null handling tests the item, not its string: an item whose ToString() returns null becomes "".
return items
.Select(item => ThrowIfNullItem(item, nullItemHandling)?.ToString())
.Where(item => nullItemHandling is NullItemHandling.Include || item is not null);
.Select(item => ThrowIfNullItem(item, nullItemHandling))
.Where(item => nullItemHandling is NullItemHandling.Include || item is not null)
.Select(item => item is null ? null : item.ToString() ?? string.Empty);
}

/// <summary>
Expand Down Expand Up @@ -260,10 +265,7 @@
}
#pragma warning restore KTSU0004 // Use Ensure.NotNull instead of manual null check

return string.Join(separator, items
.Select(item => ThrowIfNullItem(item, nullItemHandling))
.Where(item => nullItemHandling is NullItemHandling.Include || item is not null)
.Select(i => i?.ToString()));
return string.Join(separator, items.ToStringEnumerable(nullItemHandling));
}

/// <summary>
Expand Down
Loading