【问题标题】:C# Class Auto increment IDC# 类自动递增 ID
【发布时间】:2012-03-04 22:55:35
【问题描述】:

我在 C# 中创建了一个名为“Robot”的类,每个机器人都需要一个唯一的 ID 属性来赋予自己一个身份。

有没有办法为每个新的类对象创建一个自动增量 ID?因此,如果我创建了 5 个新机器人,它们的 ID 分别为 1、2、3、4、5。如果我随后销毁机器人 2 并稍后创建一个新机器人,它的 ID 将为 2。如果我添加一个第 6 个它的 ID 为 6,依此类推..

谢谢。

【问题讨论】:

  • "如果我随后销毁机器人 2 并稍后创建一个新机器人,它的 ID 将为 2。"对我来说,这听起来不像是自动增量的基本概念。
  • 机器人实例是否保存在某个数据存储中? SQL Server、Access 等

标签: c# class identity auto-increment member


【解决方案1】:

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

  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】:
    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)

    【讨论】:

      【解决方案3】:
      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。如果我按此顺序处理所有机器人,最后一个被销毁的机器人将是 no。 3,所以我创建的下一个机器人的 id 为 3,而不是预期的 1。事实上,IdOfDestroy 将保持为 3,因此下一个创建的机器人的 id 也将是 3。
      • 是的@Tudor,你是对的,很抱歉我的代码无法正常工作,非常感谢。
      【解决方案4】:

      创建一个静态实例变量,并在其上使用Interlocked.Increment(ref nextId)

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

      注意#1:使用nextId++ 仅在非并发环境中有效;即使您从多个线程分配机器人,Interlocked.Increment 也能正常工作。

      EDIT 这不涉及重复使用机器人 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&lt;int&gt; 替换为 SortedSet&lt;int&gt; 并创建一个围绕从集合中获取要重复使用的 ID 的部分进行一些调整。

      【讨论】:

      • 经典增量在单线程环境中就足够了。
      • 天哪!我不敢相信这是解决明显竞争条件的唯一答案。
      • @Tudor:在当今时代,我们并没有真正享受假设单线程环境的乐趣。
      • @A.R.学习一些基本的编程概念可能只是一个玩具问题。不需要用非阻塞锁和读写锁来填充 OP 的头部。
      • @Tudor:竞争条件是编程的基本概念,尤其是在 .NET 世界中。
      【解决方案5】:

      没有这样的内置功能。您必须自己实现它,例如持有一个位数组来标记已使用的 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 = usedIds[i];
                       usedIds[i] = true;
                       break;
                   }
               }
               return foundId;
          }
      }
      

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

      【讨论】:

        【解决方案6】:

        并非如此,但是您可以使用在类中初始化并在调用构造函数时递增的静态 int。

        class Robot()
        {
            static int nrOfInstances = 0;
        
            init _id;
        
            Robot()
            {
                _id = Robot.nrOfInstances;
                Robot.nrOfInstances++;
            }
        }
        

        (希望语法正确,这里没有编译器。)

        如果您希望重复使用已删除的机器人 ID,请不要使用计数器,而是使用静态列表并将其添加到列表中。

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

        【讨论】:

          猜你喜欢
          • 2011-04-05
          • 2012-01-09
          • 2010-10-04
          • 2015-03-10
          • 1970-01-01
          • 2018-11-05
          • 2018-02-11
          • 2015-07-05
          • 2014-10-01
          相关资源
          最近更新 更多