我写了下面的类来返回一个像掷骰子一样的随机数:
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();
}
如您所见,我将该课程称为myRoll
和mySecondRoll
两次。我想通过这样做它会创建类的单独实例并输出两个单独的数字(一个在1和6之间,另一个在1和12之间)
我遇到的问题是:
1)第一个数字总是0。
2)该类的两个实例相互干扰,即。应该在1到6之间的数字不是。
我想知道,不仅仅是如何修复代码,还想解释这里发生了什么以及为什么,谢谢。
答案 0 :(得分:5)
问题在于您将Dice类中的字段声明为static
。这意味着该变量只有一个实例,它将在应用程序中的所有类实例之间共享。
以下一行:
public int rolled = diceRoll.Next(_minNum, _maxNum);
...在您创建new Dice()
时开始运行,这意味着您尚未初始化_minNum
和_maxNum
值:这就是为什么它会给您一个{ {1}}。您可以将其转换为属性,因此代码会等待您运行,直到您要求它为止:
0
...但通常不会通过询问属性来改变属性。这种代码倾向于创建所谓的Heisenbugs,这很难追踪,因为系统的行为只是通过尝试观察它而发生变化。
所以这里有一种方法可以重新编写你的类,使用public int Rolled { get { return diceRoll.Next(_minNum, _maxNum); } }
方法来实际执行roll,以及一个允许代码在必要时继续检查最后一个roll值的属性:
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();
}
答案 1 :(得分:5)
问题二已经解决了:因为变量是静态的:
static int _minNum;
static int _maxNum;
另一方面问题一还没有回答,所以这里有:
public int rolled = diceRoll.Next(_minNum, _maxNum);
这不是一些动态调用。这是一个字段初始化,甚至会在构造函数之前设置。您可以通过第一次通过骰子调试来检查这一点。
此时_minNum
和_maxNum
仍为0,因此roll将设置为0
这可以通过将滚动转换为属性来修复:
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;
}
}
}
请注意,骰子有两个属性:Rolled
和ReRoll
。因为你的意图不清楚,我已经加上两个来说明这种行为。
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();
}
答案 2 :(得分:1)
我会改变你的课程,看起来更像以下内容:
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);
}
}
答案 3 :(得分:1)
您的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 properties
here的教程。
答案 4 :(得分:1)
您的问题是由static
成员造成的。
在static上的MSDN文档中,“虽然类的实例包含该类的所有实例字段的单独副本,但每个静态字段只有一个副本。”
答案 5 :(得分:1)
我认为这里真正的问题是你还没有完全模拟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());
//...
}