9

I am creating a class in C# called "Robot", and each robot requires a unique ID property which gives themselves an identity.

Is there any way of creating an auto incremental ID for each new class object? So, If i created 5 new robots, their IDs respectively will be 1, 2, 3, 4, 5. If I then destroy robot 2 and create a new robot later, it will have the ID of 2. And if I add a 6th it will have the ID of 6 and so on..

Thanks.

Stefan Dunn
  • 5,363
  • 7
  • 48
  • 84
  • 11
    "If I then destroy robot 2 and create a new robot later, it will have the ID of 2." That doesn't sound like the basic concept of auto-increment to me. – BoltClock Feb 13 '12 at 14:33
  • Are the robot instances persisted in some data store? SQL Server, Access, etc – Bryan Feb 13 '12 at 14:35

6 Answers6

33

Create a static instance variable, and use Interlocked.Increment(ref nextId) on it.

class Robot {
    static int nextId;
    public int RobotId {get; private set;}
    Robot() {
        RobotId = Interlocked.Increment(ref nextId);
    }
}

Note #1: using nextId++ would be valid only in non-concurrent environments; Interlocked.Increment works even if you allocate your robots from multiple threads.

EDIT This does not deal with re-using robot IDs. If you need reuse, the solution is a lot more complex: you need a list of reusable IDs, and a ReaderWriterLockSlim around the code that accesses that list.

class Robot : IDisposable {
    static private int nextId;
    static private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    static private IList<int> reuseIds = new List<int>();
    public int RobotId {get; private set;}
    Robot() {
        rwLock.EnterReadLock();
        try {
            if (reuseIds.Count == 0) {
                RobotId = Interlocked.Increment(ref nextId);
                return;
            }
        } finally {
            rwLock.ExitReadLock();
        }
        rwLock.EnterWriteLock();
        try {
            // Check the count again, because we've released and re-obtained the lock
            if (reuseIds.Count != 0) {
                RobotId = reuseIds[0];
                reuseIds.RemoveAt(0);
                return;
            }
            RobotId = Interlocked.Increment(ref nextId);
        } finally {
            rwLock.ExitWriteLock();
        }
    }
    void Dispose() {
        rwLock.EnterWriteLock();
        reuseIds.Add(RobotId);
        rwLock.ExitWriteLock();
    }
}

Note #2: If you would like to reuse smaller IDs ahead of larger IDs (as opposed to reusing IDs released earlier before IDs released later, as I coded it) you can replace IList<int> with SortedSet<int> and make a few adjustments around the parts where an ID to be reused is taken from the collection.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    A classic increment is sufficient in a single-threaded environment. – Tudor Feb 13 '12 at 14:37
  • 6
    holy crap! I can't believe this is the only answer that addresses the obvious race condition. – A.R. Feb 13 '12 at 14:39
  • 2
    @Tudor: In this day and age we don't really have the pleasure of assuming a single threaded environment. – A.R. Feb 13 '12 at 14:40
  • @A.R. It might be just a toy problem to learn some basic concepts of programming. No need to fill the OP's head with non-blocking locks and read-write locks. – Tudor Feb 13 '12 at 14:46
  • 5
    @Tudor: Race conditions are a basic concept of programming, especially in .NET world. – A.R. Feb 13 '12 at 14:56
11

This will do the trick, and operate in a nice threadsafe way. Of course it is up to you to dispose the robots yourself, etc. Obviously it won't be efficient for a large number of robots, but there are tons of ways to deal with that.

  public class Robot : IDisposable
  {
    private static List<bool> UsedCounter = new List<bool>();
    private static object Lock = new object();

    public int ID { get; private set; }

    public Robot()
    {

      lock (Lock)
      {
        int nextIndex = GetAvailableIndex();
        if (nextIndex == -1)
        {
          nextIndex = UsedCounter.Count;
          UsedCounter.Add(true);
        }

        ID = nextIndex;
      }
    }

    public void Dispose()
    {
      lock (Lock)
      {
        UsedCounter[ID] = false;
      }
    }


    private int GetAvailableIndex()
    {
      for (int i = 0; i < UsedCounter.Count; i++)
      {
        if (UsedCounter[i] == false)
        {
          return i;
        }
      }

      // Nothing available.
      return -1;
    }

And some test code for good measure.

[Test]
public void CanUseRobots()
{

  Robot robot1 = new Robot();
  Robot robot2 = new Robot();
  Robot robot3 = new Robot();

  Assert.AreEqual(0, robot1.ID);
  Assert.AreEqual(1, robot2.ID);
  Assert.AreEqual(2, robot3.ID);

  int expected = robot2.ID;
  robot2.Dispose();

  Robot robot4 = new Robot();
  Assert.AreEqual(expected, robot4.ID);
}
A.R.
  • 15,405
  • 19
  • 77
  • 123
2

Not really, however you can use a static int which you initialize in the class and is incremented when the constructor is called.

class Robot()
{
    static int nrOfInstances = 0;

    init _id;

    Robot()
    {
        _id = Robot.nrOfInstances;
        Robot.nrOfInstances++;
    }
}

(I hope the syntax is right, don't have a compiler here.)

If you want to have a removed robot ID being reused, don't use a counter, but use a static list and add it to the list.

However, what might be better is to keep the list of used IDs in another class, so you don't need the static at all. Always think twice before you use a static. You might keep the list of used IDs in a class called 'RobotCreator', 'RobotHandler', 'RobotFactory' (not like the design pattern).

Michel Keijzers
  • 15,025
  • 28
  • 93
  • 119
2

There is no such built-in functionality. You have to implement it yourself, like holding an array of bits to mark the used ids and then searching for the first unused id every time you create a new robot.

By the way, auto-increment (in a database sense) actually means that you keep incrementing the counter even if one or more of the previously used values are no longer associated to an object.

Here is some code:

public class Robot 
{
    private static const int MAX_ROBOTS = 100;
    private static bool[] usedIds = new bool[MAX_ROBOTS];
    public int Id { get; set; }

    public Robot()
    {
         this.Id = GetFirstUnused();             
    }

    private static int GetFirstUnused()
    {
         int foundId = -1;
         for(int i = 0; i < MAX_ROBOTS; i++)
         {
             if(usedIds[i] == false)
             {
                 foundId = i;
                 usedIds[i] = true;
                 break;
             }
         }
         return foundId;
    }
}

There are more sophisticated algorithms / data structures to find the first unused in less than O(N), but this is beyond the scope of my post. :)

DanK
  • 3
  • 1
  • 2
Tudor
  • 61,523
  • 12
  • 102
  • 142
1
class Robot : IDisposable
{
    static private int IdNext = 0;
    static private int IdOfDestroy = -1;

    public int RobotID
    {
        get;
        private set;
    }

    public Robot()
    {
        if(IdOfDestroy == -1)
        {
            this.RobotID = Robot.IdNext;
            Robot.IdNext++;

        }
        else
        {
            this.RobotID = Robot.IdOfDestroy;
        }
    }

    public void Dispose()
    {
        Robot.IdOfDestroy = this.RobotID;
    }
}

I hope can help you !

  • This will not work as expected. Assume I have 3 robots, initially with ids 1, 2, 3. If I dispose all of them in this order, the last destroyed one will be no. 3, so the next robot I create will have id 3, not 1 as expected. In fact, `IdOfDestroy` will remain 3, so the next created robot will also have id 3. – Tudor Feb 13 '12 at 15:43
  • yes @Tudor, you are right, i'm sorry my code will not work expected,thank you very much. – Ruiqiang Liu Feb 14 '12 at 14:42
0
public static void beAddedTo<T>(this T item, Dictionary<int, T> dic) where T : m.lib.RandId
{
    Random ran = new Random();
    var ri = ran.Next();
    while (Program.DB.Rooms.ContainsKey(ri)) ri = ran.Next();
    item.Id = ri;
    dic.Add(item.Id, item);
}

Not incremental but you can add and delete item how many time you want. (Maximum item should be lower than int.Max/2)

HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
Kaiba Zax
  • 26
  • 3