-1

I've written the following class to return a random number like rolling a dice:

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

This class is called a couple of times in my form:

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

As you can see, I call the class twice as myRoll and mySecondRoll. I thought by doing this it would create separate instances of the class and output two separate numbers (one between 1 and 6, the other 1 and 12)

The problems I am having are:

1) the first number out is always 0.

2) the two instances of the class interfere with one another, ie. the number that should be between 1 and 6 just isn't.

I'm wondering, not just how to fix the code, but would also like an explanation of what is happening here and why, thanks.

T I
  • 9,785
  • 4
  • 29
  • 51
Chris
  • 37
  • 1
  • 3
  • 3
    Asking for tutorials (or other broad questions) are not constructive, do not fit the Q&A format of Stack Exchange sites, and lead to discussion. – Austin Henley Jun 11 '13 at 21:09
  • There are several problems with this code. Your integer min and max fields should not be static, your rolled field should neither be public nor initialized the way it is. But mostly, you are correct that you need a better tutorial but unfortunately, that is not on topic for this website. I have voted to close as not constructive. – Anthony Pegram Jun 11 '13 at 21:12
  • http://www.blackwasp.co.uk/CSharpClassProperties.aspx – Learner Jun 11 '13 at 21:13
  • I'm not sure why this question is being downvoted... – T I Jun 11 '13 at 21:13
  • Don't forget that there's MSDN tutorials straigh from microsoft: http://msdn.microsoft.com/en-us/beginner/bb308750.aspx – Learner Jun 11 '13 at 21:13
  • 1
    Changing the title to be more descriptive of the *actual* problem would help. – Austin Henley Jun 11 '13 at 21:15
  • 1
    He's not only asking for tutorials, he has some coding issues as well. So, no need to downvote. – Learner Jun 11 '13 at 21:16
  • By the way be also careful with different instances of Random created at the same time. That may result in getting same random values. Thats because of the fact that the Random algorithm is based on a sort of a start value, which is set to a system timestamp in the defaut constructor. – Christian Jun 11 '13 at 21:41

6 Answers6

5

The problem is that you are declaring the fields in the Dice class as static. This means that there will only be one instance of that variable, which will be shared across all instances of the class within the application.

The following line:

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

... gets run the moment you create your new Dice(), which means that you haven't yet initialized your _minNum and _maxNum values yet: that's why it's giving you a 0. You could turn this into a property, so the code would wait to be run until you asked for it:

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

... but typically properties are not expected to change just by asking for their value. This sort of code tends to create so-called Heisenbugs, which are very difficult to track down because the system's behavior changes simply by trying to observe it.

So here's one way you might re-write your class, using a Roll() method to actually perform the roll, and a property that allows code to keep checking on the last roll's value whenever necessary:

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

(note that "die" is the singular form of "dice"). Usage:

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();
}
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Thanks. I had a feeling it might be that, however for some reason VS desperately wants those variables to be static or it bombs out. I'm guessing there's a better way to construct it that gets around this? – Chris Jun 11 '13 at 21:13
  • @Chris Are you calling it from a static method or class? – Austin Henley Jun 11 '13 at 21:14
  • @Chris: define "bombs out". Most likely it's because you're trying to access one of these fields from a static context. – StriplingWarrior Jun 11 '13 at 21:16
  • 1
    Okay, whoever went through and gave every answer a down-vote needs to explain themselves. – StriplingWarrior Jun 11 '13 at 21:18
  • Every single answer is *wrong.* When you remove static, try running* the code. Is your output within range? It shouldn't be. Also pay attention to the first comment here. "It bombs out." Why do you think that is? There's more to address than simply the static modifiers. – Anthony Pegram Jun 11 '13 at 21:21
  • @StriplingWarrior When I remove the static I get "Error 1 A field initializer cannot reference the non-static field, method, or property" – Chris Jun 11 '13 at 21:23
  • @Chris: The problem is with `public int rolled = diceRoll.Next(_minNum, _maxNum);`. If you're sure you want to do it this way, you could initialize the `rolled` field in your constructor rather than where you declare it. However, if you think about how you use dice, wouldn't it make more sense to have a `Roll` method that calls the `random.Next()` method and returns the result of a roll? People typically don't buy a new set of dice each time they want a new roll, right? – StriplingWarrior Jun 11 '13 at 21:29
  • @user414076: Most of the answers did correctly identify the fact that the behavior the OP complained of was due to the use of `static` fields, where he obviously intended non-static behaviors. They may not have detailed every step toward making the code work, but I wouldn't go so far as to say they were wrong. I'd invite you to come up with a more complete answer. – StriplingWarrior Jun 11 '13 at 22:02
  • @Chris: I updated my answer. Hopefully that explains things. Let me know if you don't understand any of it. – StriplingWarrior Jun 11 '13 at 22:12
  • I do not answer questions I vote to close. But if answers are incomplete, they are simply wrong. Since you have updated your answer, I will flip my vote. – Anthony Pegram Jun 11 '13 at 23:05
  • @StriplingWarrior Very clear answer. I understand the logic of doing it that way. Thanks for all your help. – Chris Jun 12 '13 at 14:55
5

Question Two is already aswered: because the variables are static:

static int _minNum;
static int _maxNum;

Question One on the other hand isnt answered yet, so here goes:

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

this is not some dynamic call. This is a field initialisation, and will be set even before the constructor. You can check this out by debugging through the dice the first time.

at that point both _minNum and _maxNum are still 0, so rolled will be set to 0

this can be fixed by turning rolled into a property too:

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

At the moment _minNum and _maxNum are getting set the first time because they are static, therefor when you create the second dice, they are already set.

Edit, since a recommendation was asked, this is how I'd create it:

The dice

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

Note the the dice has two properties: Rolled, and ReRoll. Because your intention is unclear, I've added both to illustrate the behavior.

Rolled is set by the constructor. If you want a new number, you can ReRoll.

If you do intentionally wanted the lifetime of a roll to be one per dice (but I dont think so) You'd remove the ReRoll method.

The dice would be called like this:

    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();
    }
Ron Sijm
  • 8,490
  • 2
  • 31
  • 48
  • This is working towards a better solution than the other answers so far because it addresses the `rolled` initializer. However, it should be clearer and self-contained what you mean to do with the current static fields (be explicit about your recommendation), and I think a better recommendation for getting the rolled value is in order (probably a method call than a property). – Anthony Pegram Jun 11 '13 at 21:28
  • This actually got it working perfectly, even if the logic of using static is incorrect and needs fixing. Thanks for your help! – Chris Jun 11 '13 at 21:34
  • You're welcome. I've added an example as requested by @user414076 – Ron Sijm Jun 11 '13 at 21:51
1

I would change your class to look more like the following:

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);
  }    
}
poy
  • 10,063
  • 9
  • 49
  • 74
  • 2
    Actually, the `Random()` static is good. You generally only want one instance of random, to get the best random results. - Otherwise when multiple randoms are created at (very) closely the same time, their output will become predictable – Ron Sijm Jun 11 '13 at 21:15
  • Agreed, and I updated my answer. – poy Jun 11 '13 at 21:29
  • Your updates are well received. Vote flipped. – Anthony Pegram Jun 11 '13 at 21:35
1

Your get/setter backing fields are marked as "static". If a variable is declared "static", the value is persisted throughout the application and shared between different instances of the type they live in.

See here.

Also,

since your class properties contain no logic, I suggest using "automatic" properties.

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

tutorial on automatic properties here.

Community
  • 1
  • 1
d.moncada
  • 16,900
  • 5
  • 53
  • 82
1

Your problem is due to the static members.

From the MSDN documentation on static, "While an instance of a class contains a separate copy of all instance fields of the class, there is only one copy of each static field."

Austin Henley
  • 4,625
  • 13
  • 45
  • 80
1

I think the real problem here is that you haven't quite modeled a Die properly.

A die has a min and max value (that define the start and end of a range) but once a die has been made you are not able to change this i.e. a six sided die isn't made into an eight sided die. As such there is no need for the public setters.

Now, not all die share the same range, this is something that is specific to each die and so these properties should belong to the instance and not be static.

Again each die object has a CurrentRoll value representing the number that is face up and this is indeed generated at random. However to change the CurrentRoll of a die you need to Roll it.

This leaves an implementation of Die looking a something like

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

and you would use it like

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

T I
  • 9,785
  • 4
  • 29
  • 51