Reputation: 2176
I am implementing a custom collection in .NET, and in doing so, am implementing the ICollection<T>
interface by proxy. Because of this, I can't actually avoid implementing this interface. Part of the interface's contract is
public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
Int32 Count { get; }
// ...
}
My collection will be able to hold more than Int32.MaxValue
items (in fact, this is an expected use case), however I'm not sure what the right thing to do is when Count
exceeds the range of an Int32.
So what should occur when ICollection<T>.Count
is called when the collection contains more than 2^31 elements? Return Int32.MaxValue
? Throw an OverflowException
? Something else?
Upvotes: 2
Views: 241
Reputation: 55519
There is no “right” answer because the official documentation is silent on this question. Any possible implementation will fail to behave properly (that is, as documented) in at least one scenario. Therefore, the best implementation of ICollection<T>.Count
in your case will depend on how Count
is used by your code, either directly or indirectly.
Starting with the obvious, if Count
is never used, then it doesn’t matter how you implement the property. You could hide the property with an explicit interface implementation that throws NotImplementedException (serving as a TODO), and revisit this later:
Int32 ICollection<T>.Count => throw new NotImplementedException();
But if Count
is called, here are some advantages and disadvantages of the two options you listed plus a couple more:
public Int32 Count => _count <= Int32.MaxValue
? (Int32)_count
: throw new NotSupportedException();
Count
’s documented contract couldn’t be fulfilled and prevents subsequent code from silently operating on an incorrect value and possibly producing garbage output.Count
in your own code are easy to spot, usages by framework and third-party APIs may be buried. For example, the LINQ .Any()
extension method invokes Count
to check whether the collection is empty. If you throw an exception only occasionally, it may be difficult to find all such indirect usages ahead of time and wrap them in try...catch
blocks or refactor your code to avoid calling these APIs.public Int32 Count => _count < Int32.MaxValue
? (Int32)_count
: Int32.MaxValue;
This is pretty much the opposite of Option 1.
.Any()
extension method) to work no matter how many items are in the collection.Count
in your own code are easy to spot, usages by framework and third-party APIs may be buried. It may be difficult to predict whether such code will behave properly when given your sentinel value—now and in the future. (The current implementation of .Any()
happens to work because it only checks whether the count is nonzero.)Int32 ICollection<T>.Count => throw new NotSupportedException();
Count
, allowing you to refactor your code to avoid calling these APIs..Any()
extension method) even when your collection contains no more than Int32.MaxValue
items.(You ruled out this option in your question. I’m providing it for completeness for anyone else who comes across this question in the future.)
.Any()
extension method is guaranteed to work no matter how many items are in the collection.Int32.MaxValue
items. For example, .Any()
is forced to always allocate an enumerator.Upvotes: 3
Reputation: 6592
I think the question is more complex than it seems at first...
If you return Int32.MaxValue
you would hide the fact that there are more elements in the collection which will lead to errors that are not easily visible in the code, e.g. calculating some average over all items or whatever. It will work but it will yield false results - so it's a very, very bad idea.
I agree with ry's comment that throwing an OverflowException
is for sure the better way to go as it clearly indicates that there are more elements than would fit in the Int32
datatype.
I've seen some code (as far as I remember is was an extension method of some sort) that implemented an additional property LongCount
which returns an Int64
. You could inherit an interface from ICollection<T>
(something like IExtendedCollection<T>
or whatever) that adds this property. In this way you leave the "problem" to the code using the collection and offer an alternative solution by providing an extended interface. As the interface inherits the original one, your collection will still work everywhere the original interface is expected.
Update:
Found it, it was Enumerable.LongCount
Upvotes: 5