6
public class List<T>
{
     public int Count
        {
            get;
        }
}

I noticed Count is an int, will the result be less than 0?

If Count could be less than 0, I have to write that:

if(myList.Count < 1)
{
  
}

otherwise I can write that:

if(myList.Count == 0)
{
  
}
Lance U. Matthews
  • 15,725
  • 6
  • 48
  • 68
Hanley
  • 91
  • 4
  • No it can't be less than zero. – WBuck Sep 25 '21 at 00:16
  • No, count can't be less than zero. This is a common sense question. How could you possibly have fewer than zero items in a list? – Ken White Sep 25 '21 at 00:17
  • @Dai I think he was using that as an example of the dotnet's implementation. – WBuck Sep 25 '21 at 00:17
  • 2
    is this `System.Collections.Generic.List` or is it your own `List`? – bolov Sep 25 '21 at 00:18
  • @bolov System.Collections.Generic.List – Hanley Sep 25 '21 at 00:22
  • 2
    The answer is no, you can move on to bigger and better things now – TheGeneral Sep 25 '21 at 00:23
  • 2
    @Ken White Just to be on the safe side,because I noticed the Count is a int but not a uint – Hanley Sep 25 '21 at 00:24
  • @Hanley It uses `int`/ `Int32` instead of `UInt32` / `uint` because the (now deprecated) Common Language Specification does not allow public use of unsigned types (because the BCL is intended to be consumed by languages other than C#, and those languages might not support unsigned types - surprisingly _many_ languages do not support unsigned types, (even [Java](https://www.youtube.com/watch?v=1JZnj4eNHXE)) hence the restriction. But since .NET Core methinks the concept of CLS is dead - in any event **do not worry about it**. – Dai Sep 25 '21 at 00:28
  • There is no *safe side* in this case. You can't possibly have fewer than zero items in a list under any circumstances. Grab a pencil and paper. Write a list of the numbers 1 through 5. Start erasing them one by one. When you get back to a blank page, erase one more number off the list that you've erased entirely. Let me know how you did so if you figure it out. – Ken White Sep 25 '21 at 00:30
  • 1
    Of course, `< 0` is _theoretically_ possible (since if you use the class in a non-threadsafe way then "all bets are off"). But if that is the case, you will have bigger issues - so don't worry about it. Just check `== 0`. – mjwills Sep 25 '21 at 00:40
  • @KenWhite Simple: spam entries into and out-of a single `List` concurrently using multiple-threads - though the fact it's unsigned is irrelevant because if it was unsigned it would just wrap-around to 4 billion or similar. – Dai Sep 25 '21 at 00:41
  • 1
    `SomeList.GetType().GetField("_size", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(SomeList, -1);` – TheGeneral Sep 25 '21 at 00:45
  • I’m voting to close this question because Stack Overflow is not intended to replace existing documentation. – Karl Knechtel Sep 25 '21 at 02:06
  • @Ken White I am just curious on it. – Hanley Sep 25 '21 at 02:16
  • 1
    Related: [Why check whether List.Count <= 0?](https://stackoverflow.com/q/36595324/150605)*, [Difference between list.Count > 0 and list.Count != 0](https://stackoverflow.com/q/33050573/150605), [Why does .NET use int instead of uint in certain classes?](https://stackoverflow.com/q/782629/150605). * I voted that question as a duplicate of this one because those answers focus more on _alternatives_ to testing `Count` (i.e. `Any()`) and gloss over _why_ `int Count` can(not) be negative, although now I'm having second thoughts about if these really are true duplicates. – Lance U. Matthews Sep 25 '21 at 22:46

3 Answers3

13

From the comments:

Just to be on the safe side, because I noticed the Count is a int but not a uint

TL;DR:

When the List<T> class was designed, Microsoft's .NET class library design rules meant that UInt32 (aka uint) could not be used for any public members, so instead Int32 (aka int) was used.


Longer answer:

  • The reason why Count is an int (aka Int32) property instead of a uint (or UInt32) property is because the List<T> type was defined when the Common Language Specification (the "CLS") was still being respected by Microsoft.
    • Ostensibly, redistributable and reusable libraries should only use CLS-approved data-types and should avoid using non-CLS-compliant data-types.
      • Int32 is CLS-compliant.
      • UInt32 is not CLS-compliant.
    • The CLS exists because (at the time, around 2000-2005) Microsoft had this grand design for using .NET/CLR as the runtime and underlying type-system for many radically different programming languages - not just C# and VB.NET, but also Java (yup), OCaml-style functional languages like F#, and even JavaScript, Python, and PHP.
    • ...and many of those languages simply don't support unsigned integer types; in-fact, high-profile languages like Java still don't support unsigned types.
    • ...so in order to accommodate languages used by masochists, like Java, the main .NET class library had to be designed with restrictions on the possible data-types they could use, which means they have to use signed integers to represent unsigned values.
    • Therefore, if Count was typed as UInt32 instead of Int32 then users of those other languages couldn't use List<T>.Count at all.
  • Since .NET Core was released, I don't believe there has been any official deprecation of the Common Language Specification, but even if Microsoft hasn't officially abandoned it, the rest of the .NET ecosystem seems to have.

Can List<T>.Count ever be negative or otherwise invalid?

Provided you use a List<T> instance in a safe manner, then no, the .Count property will always return a value in the range 0 to 2^31 (the actual maximum count of a List<T> is a different question which is already answered here).

However, if you use a List<T> in a thread-unsafe manner (such as having 2 or more threads concurrently adding and removing items from a list without using lock correctly), or if you use tricks like reflection to override the .Count property's field value then yes, you can break things. So don't do that :)

Common Language Specification (CLS)

The motivation for the CLS is described in this article:

  • To enable full interoperability scenarios, all objects that are created in code must rely on some commonality in the languages that are consuming them (are their callers).
  • Since there are numerous different languages, .NET has specified those commonalities in something called the Common Language Specification (CLS).

The Common Language Specification is described in this MSDN article, which has a list of types built-in to .NET but which are verboten for use in CLS-compliant frameworks and redistributable libraries.

  • System.SByte aka sbyte.
    • Signed octet value.
    • This is the only signed, as opposed to unsigned, type prohibited by the CLS.
    • Curiously, Java actually has a signed byte type.
  • System.UInt16 aka ushort.
  • System.UInt32 aka uint.
  • System.UInt64 aka ulong.
  • System.UIntPtr.

Also:

  • The rules for CLS compliance apply only to a component's public interface, not to its private implementation.
  • For example, unsigned integers other than Byte are not CLS-compliant [...].
Dai
  • 141,631
  • 28
  • 261
  • 374
3

No, the reference source (which only apply for .NET Framework) for that property is

public int Count {
   get {
       Contract.Ensures(Contract.Result<int>() >= 0);
       return _size; 
   }
}

which due to how Contract.Ensures works, guarantee it will never return less than zero.

As for .NET Core, that property is simply

public int Count => _size;

_size itself can't ever get below zero according to all the assignments in the code.

Martheen
  • 5,198
  • 4
  • 32
  • 55
  • 1
    Catch: Code Contracts are not supported in .NET Core nor .NET 5+. – Dai Sep 25 '21 at 00:30
  • 2
    "`_size` itself can't ever get below zero according to all the assignments in the code." - **it can** if you use a `List` in thread-unsafe way. – Dai Sep 25 '21 at 00:42
2

Of course it can, why even ask. Here is some blasphemy:

[TestFixture]
public class TotalGarbageTests
{
    [Test]
    public void Blasphemy()
    {
        var list = new List<int>();
        list.GetType().GetField("_size", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(list, -666);
        Assert.AreEqual(-666, list.Count);
    }
}

PS Now you are probably corrupted by knowledge, but meh, whatever.

eocron
  • 6,885
  • 1
  • 21
  • 50
  • 4
    I wonder if maybe we _should_ teach reflection early-on, just so people learn to to never use it… – Dai Sep 25 '21 at 02:12
  • There is darker black magic than just reflection if you remember you have access to modifying IL and can blow up kernel with admin rights ****sinister laugh**** – eocron Sep 25 '21 at 02:15
  • You can’t “blow up the [OS] kernel” by manipulating IL because you can’t run the CLR inside an OS kernel. – Dai Sep 25 '21 at 02:30
  • 3
    While I appreciate good trolling this is really bad guidance... unfortunately there is very high chance OP now will claim that all checks for `.Count` (and `.Count()`... and `.Length`) should check for negative values too and annoy everyone around them. – Alexei Levenkov Sep 25 '21 at 05:27