【问题标题】:Understanding classes and using Random [closed]理解类和使用随机[关闭]
【发布时间】:2013-06-11 21:07:37
【问题描述】:

我编写了以下类来返回一个随机数,就像掷骰子一样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GameTest
{
    class Dice
    {
    public int publicMinNum
    {
        get { return _minNum; }
        set { _minNum = value; }
    }

    public int publicMaxNum
    {
        get { return _maxNum; }
        set { _maxNum = value; }
    }

    static int _minNum;
    static int _maxNum;

    static Random diceRoll = new Random();
    public int rolled = diceRoll.Next(_minNum, _maxNum);
 }
}

这个类在我的表单中被调用了几次:

    private void btnPushMe_Click(object sender, EventArgs e)
    {
        Dice myRoll = new Dice();
        myRoll.publicMinNum = 1;
        myRoll.publicMaxNum = 7;

        lblMain.Text = myRoll.rolled.ToString();

        Dice mySecondRoll = new Dice();
        mySecondRoll.publicMinNum = 1;
        mySecondRoll.publicMaxNum = 13;

        lblMain2.Text = mySecondRoll.rolled.ToString();
    }

如您所见,我将课程称为myRollmySecondRoll 两次。我认为这样做会创建类的单独实例并输出两个单独的数字(一个在 1 和 6 之间,另一个在 1 和 12 之间)

我遇到的问题是:

1) 第一个输出的数字总是 0。

2) 类的两个实例相互干扰,即。应该在 1 到 6 之间的数字不是。

我想知道,不仅仅是如何修复代码,还想解释一下这里发生了什么以及为什么,谢谢。

【问题讨论】:

  • 要求教程(或其他宽泛的问题)不具建设性,不符合 Stack Exchange 网站的问答格式,并导致讨论。
  • 这段代码有几个问题。你的整数最小值和最大值字段不应该是静态的,你的滚动字段既不应该是公开的,也不应该按原样初始化。但大多数情况下,您需要一个更好的教程是正确的,但不幸的是,这不是本网站的主题。我已投票结束,因为没有建设性。
  • 将标题更改为更能描述实际问题会有所帮助。
  • 他不仅要教程,而且还有一些编码问题。所以,没有必要投反对票。

标签: c# class


【解决方案1】:

问题是您将 Dice 类中的字段声明为 static。这意味着该变量将只有一个实例,它将在应用程序内该类的所有实例之间共享。

下面一行:

public int rolled = diceRoll.Next(_minNum, _maxNum);

...在您创建new Dice() 的那一刻开始运行,这意味着您尚未初始化您的_minNum_maxNum 值:这就是它给您0 的原因。你可以把它变成一个属性,所以代码会等待运行,直到你要求它:

public int Rolled { get { return diceRoll.Next(_minNum, _maxNum); } }

...但通常不会仅通过询问属性值来更改属性。这种代码往往会创建所谓的Heisenbugs,这很难追踪,因为系统的行为只是通过尝试观察而改变。

因此,您可以通过以下一种方式重新编写您的类,使用Roll() 方法实际执行滚动,以及允许代码在必要时继续检查最后滚动的值的属性:

public class Die
{

    // Using a constructor makes it obvious that you expect this
    // class to be initialized with both minimum and maximum values.
    public Die(int minNum, int maxNum)
    {
        // You may want to add error-checking here, to throw an exception
        // in the event that minNum and maxNum values are incorrect.

        // Initialize the values.
        MinNum = minNum;
        MaxNum = maxNum;

        // Dice never start out with "no" value, right?
        Roll();
    }

    // These will presumably only be set by the constructor, but people can
    // check to see what the min and max are at any time.
    public int MinNum { get; private set; }

    public int MaxNum { get; private set; }

    // Keeps track of the most recent roll value.
    private int _lastRoll;

    // Creates a new _lastRoll value, and returns it.
    public int Roll() { 
        _lastRoll = diceRoll.Next(MinNum, MaxNum);
        return _lastRoll;
    }

    // Returns the result of the last roll, without rolling again.
    public int LastRoll {get {return _lastRoll;}}

    // This Random object will be reused by all instances, which helps
    // make results of multiple dice somewhat less random.
    private static readonly Random diceRoll = new Random();
}

(注意“die”是“dice”的单数形式)。用法:

private void btnPushMe_Click(object sender, EventArgs e)
{
    Die myRoll = new Die(1, 7);
    lblMain.Text = myRoll.Roll().ToString();

    Die myRoll2 = new Die(1, 13);
    lblMain2.Text = mySecondRoll.Roll().ToString();
}

【讨论】:

  • 谢谢。我有一种感觉,但出于某种原因,VS 迫切希望这些变量是静态的,否则它会爆炸。我猜有更好的方法来构建它来解决这个问题?
  • @Chris 你是从静态方法还是类调用它?
  • @Chris:定义“轰炸”。很可能是因为您试图从静态上下文中访问这些字段之一。
  • 好吧,无论谁通过并给每个答案都投了反对票,都需要解释自己。
  • 每一个答案都是错误的。 当您删除静态时,请尝试运行*代码。你的输出在范围内吗?不应该。还要注意这里的第一条评论。 “它爆炸了。”你认为这是为什么?除了静态修饰符之外,还有更多需要解决的问题。
【解决方案2】:

已经回答了问题二:因为变量是静态的:

static int _minNum;
static int _maxNum;

另一方面,问题一还没有回答,所以这里是:

public int rolled = diceRoll.Next(_minNum, _maxNum);

这不是一些动态调用。这是一个字段初始化,甚至会在构造函数之前设置。您可以通过第一次通过骰子调试来检查这一点。

此时_minNum_maxNum 仍为0,因此rolled 将设置为0

这也可以通过将rolled变成一个属性来解决:

    public int rolled
    {
        get { return diceRoll.Next(_minNum, _maxNum); }
    }

目前_minNum_maxNum 正在第一次设置,因为它们是静态的,因此当您创建第二个骰子时,它们已经设置好了。

编辑,既然有人提出建议,我就是这样创建它的:

骰子

class Dice
{
    private static Random diceRoll = new Random();

    private int _min;
    private int _max;
    public int Rolled { get; private set; }

    public Dice(int min, int max)
    {
        _min = min;
        _max = max;

        // initializes the dice
        Rolled = diceRoll.Next(_min, _max);
    }

    public int ReRoll
    {
        get
        {
            Rolled = diceRoll.Next(_min, _max);
            return Rolled;
        }
    }
}

请注意,骰子有两个属性:RolledReRoll。由于您的意图不清楚,我添加了两者来说明行为。

Rolled 由构造函数设置。如果你想要一个新号码,你可以ReRoll

如果您确实希望掷骰子的生命周期为每个骰子一个(但我不这么认为),您可以删除 ReRoll 方法。

骰子会这样调用:

    private static void Main(string[] args)
    {
        Dice myRoll = new Dice(1, 7);

        // All the same
        var result1 = myRoll.Rolled.ToString();
        var result2 = myRoll.Rolled.ToString();
        var result3 = myRoll.Rolled.ToString();

        // something new
        var result4 = myRoll.ReRoll.ToString();

        Dice mySecondRoll = new Dice(1, 13);
        var result = mySecondRoll.ReRoll.ToString();
    }

【讨论】:

  • 这是一个比迄今为止其他答案更好的解决方案,因为它解决了 rolled 初始化程序。但是,您对当前静态字段的含义应该更加清晰和独立(明确您的建议),并且我认为获得滚动值的更好建议是有序的(可能是方法调用而不是属性)。
  • 这实际上让它完美地工作,即使使用静态的逻辑不正确并且需要修复。感谢您的帮助!
  • 不客气。我根据@user414076 的要求添加了一个示例
【解决方案3】:

您的 get/setter 支持字段被标记为“static”。如果变量声明为“static”,则该值将在整个应用程序中保持不变,并在它们所在类型的不同实例之间共享。

here

还有,

由于您的类属性不包含逻辑,我建议使用“automatic”属性。

    class Dice
    {
      public int publicMinNum { get; set; }
      public int publicMaxNum { get; set; }
      Random diceRoll = new Random();
      public int rolled = diceRoll.Next(publicMinNum , publicMaxNum );
    }

关于automatic propertieshere的教程。

【讨论】:

    【解决方案4】:

    您的问题是由static 成员引起的。

    来自static 上的 MSDN 文档,“虽然类的实例包含该类的所有实例字段的单独副本,但每个静态字段只有一个副本。”

    【讨论】:

      【解决方案5】:

      我会将您的课程更改为如下所示:

      class Dice
      {
        // These are non-static fields. They are unique to each implementation of the
        // class. (i.e. Each time you create a 'Dice', these will be "created" as well.
        private int _minNum, _maxNum;
      
        // Readonly means that we can't set _diceRand anywhere but the constructor.
        // This way, we don't accidently mess with it later in the code.
        // Per comment's suggestion, leave this as static... that way only one
        // implementation is used and you get more random results.  This means that
        // each implementation of the Dice will use the same _diceRand
        private static readonly Random _diceRand = new Random();
      
        // A constructor allows you to set the intial values.
        // You would do this to FORCE the code to set it, instead
        // of relying on the programmer to remember to set the values
        // later.
        public Dice(int min, int max)
        {
          _minNum = min;
          _maxNum = max;
        }
      
        // Properties
        public Int32 MinNum
        {
          get { return _minNum; }
          set { _minNum = value; }
        }
      
        public Int32 MaxNum
        {
          get { return _maxNum; }
          set { _maxNum = value; }
        }
      
        // Methods
        // I would make your 'rolled' look more like a method instead of a public
        // a variable.  If you have it as a variable, then each time you call it, you
        // do NOT get the next random value.  It only initializes to that... so it would
        // never change.  Using a method will have it assign a new value each time.
        public int NextRoll()
        {
          return _diceRand.Next(_minNum, _maxNum);
        }    
      }
      

      【讨论】:

      • 其实Random()这个静态的不错。您通常只需要一个随机实例,以获得最佳随机结果。 - 否则,当(非常)接近同时创建多个随机数时,它们的输出将变得可预测
      • 同意,我更新了我的答案。
      • 您的更新很受欢迎。投票翻转。
      【解决方案6】:

      我认为这里真正的问题是您没有完全正确地建模 Die。

      骰子有一个最小值和最大值(定义范围的开始和结束),但是一旦制作了骰子,您就无法更改它,即六面骰子不会变成八面骰子.因此,不需要公共设置者。

      现在,并非所有骰子都共享相同的范围,这是特定于每个骰子的,因此这些属性应该属于实例而不是static

      同样,每个骰子对象都有一个CurrentRoll 值,表示正面朝上的数字,这确实是随机生成的。但是要更改模具的CurrentRoll,您需要Roll

      这使得 Die 的实现看起来像

      class Die
      {
          private static Random _random;
      
          public int CurrentRoll { get; private set; }
      
          public int Min { get; private set; }
      
          public int Max { get; private set; }
      
          public Die(int min, int max)
          {
              Min = min;
              Max = max;
              Roll();
          }
      
          public int Roll()
          {
              CurrentRoll = _random.Next(Min, Max+1); // note the upperbound is exlusive hence +1
              return CurrentRoll;
          }
      }
      

      你会像这样使用它

      public static void Main()
      {
          Die d1 = new Die(1, 6);
          Die d2 = new Die(1, 6);
      
          Console.WriteLine(d1.Roll());
          Console.WriteLine(d2.Roll());
          //...
      }
      

      demo

      【讨论】:

        猜你喜欢
        • 2015-03-16
        • 2017-02-11
        • 2016-12-29
        • 2016-04-30
        • 2011-04-26
        • 2016-09-02
        • 2017-05-20
        • 1970-01-01
        • 2018-12-18
        相关资源
        最近更新 更多