C#类 自动递增ID

9
我正在使用C#创建一个名为“Robot”的类,每个机器人都需要一个唯一的ID属性来赋予自己身份。
有没有办法为每个新的类对象创建自动增量ID?因此,如果我创建了5个新的机器人,它们的ID分别为1、2、3、4、5。如果我然后销毁机器人2并稍后创建一个新的机器人,它将具有ID 2。如果我添加第6个,它将具有ID 6,依此类推...
谢谢。

11
如果我摧毁了机器人2并稍后创建一个新的机器人,它将具有ID 2。这听起来不像自动递增的基本概念。 - BoltClock
机器人实例是否存储在某个数据存储中?SQL Server,Access等。 - Bryan
6个回答

33
创建一个静态实例变量,然后在其上使用Interlocked.Increment(ref nextId)
class Robot {
    static int nextId;
    public int RobotId {get; private set;}
    Robot() {
        RobotId = Interlocked.Increment(ref nextId);
    }
}

注意:仅在非并发环境中,使用nextId++是有效的;Interlocked.Increment可以处理您从多个线程分配机器人的情况。

编辑:这不涉及重新使用机器人ID。如果需要重用,解决方案要复杂得多:您需要一个可重用ID列表,并在访问该列表的代码周围使用ReaderWriterLockSlim

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();
    }
}

注意2:如果您希望在重用ID时优先使用较小的ID(而不是像我编码的那样先重用早期发布的ID再重用后期发布的ID),则可以将IList<int>替换为SortedSet<int>,并在从集合中获取要重用的ID的部分进行一些调整。


1
在单线程环境中,经典的增量就足够了。 - Tudor
6
哇靠!我不敢相信这是唯一一个解决明显竞态条件的答案。 - A.R.
2
@Tudor:在当今这个时代,我们不能再假设一个单线程的环境了。 - A.R.
@A.R. 这可能只是一个玩具问题,用于学习编程的一些基本概念。没有必要向 OP 提供非阻塞锁和读写锁等高级概念。 - Tudor
5
@Tudor:竞态条件是编程的基本概念,特别是在.NET世界中。 - A.R.

11

这样做可以解决问题,并以一种良好的线程安全方式运行。当然,由您自己处理机器人的处置等事宜。显然,对于大量机器人来说效率不高,但有很多方法可以解决这个问题。

  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;
    }

还有一些测试代码,以加深认识。

[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);
}

2

不算完全是,但是你可以在类中使用静态整数,在构造函数被调用时将其初始化并递增。

class Robot()
{
    static int nrOfInstances = 0;

    init _id;

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

如果你想重复使用一个已删除的机器人ID,不要使用计数器,而应使用静态列表并将其添加到列表中。

然而,更好的做法是将已使用的ID列表保留在另一个类中,这样就根本不需要使用静态。在使用静态之前,一定要三思而后行。你可以将已使用的ID列表保存在名为“RobotCreator”、“RobotHandler”、“RobotFactory”(不像设计模式)的类中。


2

没有这样的内置功能。你需要自己实现,例如持有一个位数组来标记已使用的id,然后每次创建新机器人时搜索第一个未使用的id。

顺便说一下,自增(在数据库中的意义上)实际上意味着即使先前使用过的一个或多个值不再与对象相关联,你仍然保持递增计数器。

这里是一些代码:

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;
    }
}

有更复杂的算法/数据结构可以在小于O(N)的时间内找到第一个未使用的,但这超出了我的帖子范围。 :)


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;
    }
}

希望能对你有所帮助!


这不会按预期工作。假设我有3个机器人,最初的ID为1、2、3。如果我按照这个顺序处理它们,最后一个被销毁的将是3号,因此下一个我创建的机器人将具有ID 3,而不是预期的1。实际上,“IdOfDestroy”将保持为3,因此下一个创建的机器人也将具有ID 3。 - Tudor
是的,@Tudor,你说得对,很抱歉我的代码无法按预期工作,非常感谢你。 - Ruiqiang Liu

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);
}

不是增量的,但你可以随意添加和删除项目。 (最大项目数量应低于 int.Max/2)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接