• Advertisement

Best practice for passing data in C#?

Recommended Posts

What are some good practices for passing around data in C#? What are the pros and cons of using custom container classes versus a list or a dictionary or array?

Share this post


Link to post
Share on other sites
Advertisement

Lists are excellent for storing variable amounts of data of a specific type.

Dictionaries are excellent for storing variable amounts of data of a specific type when you need to index it by another type.

Arrays are excellent for storing a specific amount of data of a specific type.

Custom classes are excellent for storing variable or specific amounts of data of multiple types.

 

A good rule of thumb is this: don't reinvent the wheel. The List, Dictionary, and Array classes are highly optimized and the odds are they will work for most applications. That said, sometimes custom classes are just more convenient so don't be afraid to make them when you need them.

Also, I should point out that when "passing around" reference objects via function calls and the like you aren't actually passing the entire object, just a pointer to the object in memory. If you meant cross-class communication instead, that is a different beast, but I don't want to misinterpret what you said so feel free to ask for more details if that's the case.

Share this post


Link to post
Share on other sites

Basically what cjmarsh said.  Use the standard System.Collections.Generic collections (List, Stack, Queue, Dictionary, HashSet) for most things and only make a custom collection if it has some special behavior you need.  You probably won't need any other collections for a long time.  The main container class I can think of that I've written myself is a priority queue for A*.

However, since I remember posting a reply in your thread about RPG stats (comparing dictionaries to classes-with-fields), I want to make sure we're on the same page:  When we say 'custom container class' we mean a type that's specifically meant to contain data (usually all of the same type), without the container caring about the semantics of any element (i.e. the Dictionary doesn't know that it's containing RPG stats; it just sees keys and values and doesn't care what they mean).  Usually every element in a container is treated the same way by the container's code.  In your thread about RPG stats, the suggestion I made about having each stat as a field in a class would NOT be considered a 'container' class.  Each field in a class like that has a specific meaning and is treated differently by the program.

Usually you can identify when something is a container class or not by if it has some of these typical functions: Add, Remove, Insert, Push, Pop, Peek, Enqueue, Dequeue, or a square-bracket indexer.  If it just has a bunch of fields that you can directly get or set then we don't typically call those containers.

Edited by Nypyren

Share this post


Link to post
Share on other sites

Yeah I redid a bit of a class that holds Stats for a character and incorporated some of your advice. Though quick and dirty. It already feels better.
I added a bit of functionality but some of it is probably unnecessary. Handles lvl, exp, modifiers from status ailments or equipment etc.

So from what you are saying it would probably be best to pass stats or other data in dicts lists or arrays? Ex: StatSheet.Get can send a dictionary<statID, value>


	public class StatSheet
{
    public int
        Level = 0,
        EXP = 0,
        
        HP = 0,
        HPMax = 0,
	        SP = 0,
        SPMax = 0,
	        Strength = 0,
        Speed = 0,
        Soul = 0;
	    private Dictionary <MID, Modifier> mModifiers = new Dictionary <MID, Modifier>();
	    public StatSheet()
    {
        mModifiers = new Dictionary<MID, Modifier>();
    }
	    public StatSheet(Dictionary<SID, int> newStats)
    {
        Set(newStats);
        mModifiers = new Dictionary<MID, Modifier>();
    }
	    public void Set(Dictionary<SID, int> newStats)
    {
        foreach (KeyValuePair<SID, int> stat in newStats)
        {
            this.Set(stat.Key, stat.Value);
        }
    }
	    public void Set(SID statID, int value)
    {
        switch (statID)
        {
            case SID.Level:
                Level = value;
                break;
            case SID.EXP:
                EXP = value;
                break;
	
            case SID.HP:
                HP = value;
                break;
            case SID.HP_Max:
                HPMax = value;
                break;
            case SID.SP:
                SP = value;
                break;
            case SID.SP_Max:
                SPMax = value;
                break;
	
            case SID.Strength:
                Strength = value;
                break;
            case SID.Speed:
                Speed = value;
                break;
            case SID.Soul:
                Soul = value;
                break;
            default:
                Debug.LogError("Nonexistent Stat Type Added");
                break;
	        }
    }
	    public int Get(SID statID)
    {
        switch (statID)
        {
            case SID.Level:
                return Level;
            case SID.EXP:
                return EXP;
	
            case SID.HP:
                return HP;
            case SID.HP_Max:
                return HPMax;
            case SID.SP:
                return SP;
            case SID.SP_Max:
                return SPMax;
	
            case SID.Strength:
                return Strength;
            case SID.Speed:
                return Speed;
            case SID.Soul:
                return Soul;
            default:
                Debug.LogError("StatSheet: Cannot find stat type:" + statID.ToString());
                return 0;
	        }
    }
	    public Dictionary<SID,int> Get()
    {
        Dictionary<SID, int> rawStats = new Dictionary<SID, int>();
        rawStats.Add(SID.Level, Level);
        rawStats.Add(SID.EXP, EXP);
	        rawStats.Add(SID.HP_Max, HPMax);
        rawStats.Add(SID.SP_Max, SPMax);
	        rawStats.Add(SID.Strength, Strength);
        rawStats.Add(SID.Speed, Speed);
        rawStats.Add(SID.Soul, Soul);
	        return rawStats;
	
    }
	    public void Add(SID statID, int value)
    {
        int newValue = this.Get(statID) + value;
        this.Set(statID, newValue);
    }
	    public void Add(Dictionary<SID, int> stats)
    {
        foreach (KeyValuePair<SID, int> stat in stats)
        {
            this.Add(stat.Key, stat.Value);
        }
    }
	    public Dictionary<MID, Modifier> Modifiers
    {
        get{return mModifiers;}
        set{value = mModifiers;}
    }
	    
	    public void AddModifier(MID modID, Modifier modifier)
    {
        mModifiers.Add(modID, modifier);
    }
   
    public void RemoveModifier(MID modID)
    {
        mModifiers.Remove(modID);
    }
}

Share this post


Link to post
Share on other sites

You're on the right track but I'd recommend taking another step back and looking at what that class actually does. You have a list of integers and a series of functions that gets and sets the value of them based on another index. Ignoring for a moment the Dictionary of modifiers, doesn't that behavior sound just like what a Dictionary itself does? In fact, a Dictionary is just a class with methods similar to your own but the get and set operations are abstracted away in brackets and the assignment operator. Consider instead just using two Dictionaries, one for the stats and one for the modifiers.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Advertisement
  • Advertisement
  • Popular Tags

  • Advertisement
  • Popular Now

  • Similar Content

    • By Manuel Berger
      Hello fellow devs!
      Once again I started working on an 2D adventure game and right now I'm doing the character-movement/animation. I'm not a big math guy and I was happy about my solution, but soon I realized that it's flawed.
      My player has 5 walking-animations, mirrored for the left side: up, upright, right, downright, down. With the atan2 function I get the angle between player and destination. To get an index from 0 to 4, I divide PI by 5 and see how many times it goes into the player-destination angle.

      In Pseudo-Code:
      angle = atan2(destination.x - player.x, destination.y - player.y) //swapped y and x to get mirrored angle around the y axis
      index = (int) (angle / (PI / 5));
      PlayAnimation(index); //0 = up, 1 = up_right, 2 = right, 3 = down_right, 4 = down

      Besides the fact that when angle is equal to PI it produces an index of 5, this works like a charm. Or at least I thought so at first. When I tested it, I realized that the up and down animation is playing more often than the others, which is pretty logical, since they have double the angle.

      What I'm trying to achieve is something like this, but with equal angles, so that up and down has the same range as all other directions.

      I can't get my head around it. Any suggestions? Is the whole approach doomed?

      Thank you in advance for any input!
       
    • By Alexander Nazarov
      Hello. I'm newby in Unity and just start learning basics of this engine. I want to create a game like StackJump (links are below). And now I wondering what features do I have to use to create such my game. Should I use Physics engine or I can move objects changing transform manually in Update().
      If I should use Physics can you in several words direct me how can I implement and what I have to use. Just general info, no need for detailed description of developing process.
      Game in PlayMarket
      Video of the game
    • By Dave Haylett
      Hi all. My project is coming along wonderfully, and am starting to consider alpha deployment, and would like your advice.
      My project need access to 10,000 small PNG image files at runtime, each is only a few kilobytes each, which during development I used to load in directly from a fixed path on my HDD whenever one was needed (obviously not a solution for go-live), using something like this:
      img = new WriteableBitmap(new BitmapImage(new Uri(@screenshotsPath + filename)));
      The image would then be blitted onto a buffer screen, etc. etc. At a time, a few dozen would be being used.
      Now I'm thinking about deployment, and also when I produce an update to my app, there could be more images to add to the folders. So I'm considering the best way of a) deploying the images to the user as part of the project, and b) how to most easily handle updates to the app, whereby more images will be added.
      I have just experimented with adding them all as a Resource (!). This inflated the exe from 10mb to 100mb (not a major problem), increased the compile time from 3 secs to 30 secs (annoying), increased RAM usage from 500mb to 1.5gb (not a major problem either), but means that it solves my fixed directory issue, distribution issue, and update issue, simply by having the files all stuck into the executable. Here's the new code I'm using:
      img = BitmapFactory.FromResource("Shots/" + filename);
      The next thing I was going to try was to mark them as Content > Copy if Newer. This would resolve the executable size and RAM usage (and also the directory issue as well), however it seems that I'd need to highlight them all, and move them from Resource to Content. As an up-front job this isn't too bad, but as I add new images to the project, I'll need to go in and do this every time, which gets annoying, as the VS2015 default is Resource. Also, I'm not sure how this would work in terms of updates. Would something like ClickOnce deployment recognise new PNGs and install them to the users?
       
      I also have 3,000 ZIP files (~500kb each) which also need deploying and updating in the same way. These are currently read directly from my HDD until I can find a permanent solution for adding these to the project as well.
      Can anyone thing of a better way of doing what I'm trying to achieve?
      Thanks for any help folks.
       
    • By Felis Nigripes
      I'm doing a test quest.
      The player gets a quest from an NPC to bring him fish.

      Once the player picks up the fish, the original NPC gets replaced by a new one with a new conversation trigger. The NPC tells the Player "Well done" and should give 200xp.

      The script tells the xp counter to go up by making a reference to the gameobject that holds the text component
       
      But it throws this error:
       

       
      I'm aware that the error may hide in plain sight. I just have to sort this out, since I'm writing the AI at the same time, and the time it takes to resolve everyone of these errors is tremendous.
      Plus, I think I'll learn something. I've been having trouble with some basic functionalities recently. There might be something wrong with my understanding on how programming works.
       
      Glad if someone could help (:
       
       
       
      Edit: I'm fully aware that the update function requires an input. I call the function in the editor when the dialogue ends, it still doesn't work.
       
    • By Vu Chi Thien
      Hi fellow game devs,
      With the help of  @CombatWombat in my previous post about clutch modeling, I now have a solid direction for the modeling the clutch. The way I'm doing it is having 2 clutch states: locked and unlocked. EngineRPM and torque will be calculated separately in each state. My problem right now is the logic and code for specifying locking and unlocking.
      The condition for locking is when (engineSpeed - drivetrainSpeed) in previous update cross zero (different sign) with the current update (to determine if engineSpeed = drivetrainSpeed or not in-between updates) and engineTorque <= clutchTorque.
      The condition for unlocking is when engineTorque > clutchTorque.
      The diagram looks roughly like this (taken from matlab website with the similar implementation):

       
      However, the 2 conditions are triggers for switching states, not for determine the current state to be in, so in the end my clutch state just jumped around. I don't have a lot of experience in doing state machine, so can some one give me rough code of how to implement this? Below is my rough code:
      speedError = engineSpeed - drivetrainSpeed; if ((Math.Sign(speedError) != Math.Sign(deltaW) && currentTotalEngineTorque <= clutchReactTorque)) { clutchLocked = true; } else clutchLocked = false; deltaW = speedError; //end of update I think the main struggle is the cross zero. Because cross zero is the "trigger condition" to check if the clutch should lock when it is slipping, not the condition for continuous locking, while the code I have above is the "continuous condition" saying "this condition is true then it is locked/unlocked". Another word, if the clutch is slipping, the condition above would decide if it's locked or not, but once it is locked, the cross zero condition is not true anymore (since speedError and deltaW have same sign as engineSpeed == drivetrainSpeed when clutch is locked). I'm sorry that I cannot explain this better as English is not my first language.
  • Advertisement