4

I am trying to write a class for privileges/permissions.

I am trying to use the Binary base permission System in C#.

I got it where I believe it should work. However, it is not working as expected.

After, I stepped thru my code I can see one issue in the method called _mapPermissions the line if (dic.ContainsKey(vv)) returns false every time.

the key is an instance of a class which have 2 values (ie. secionName, KeyIndex)

This is my entire class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MySql.Data.MySqlClient;

namespace POS
{
    class Roles
    {

        private Dictionary<PermissionsKey, int> systemPermissions = new Dictionary<PermissionsKey, int>();
        private Dictionary<PermissionsKey, int> userPermissions = new Dictionary<PermissionsKey, int>();

        public Roles()
        {
            this._getSystemPrivleges();
            this._getUserPrivleges();

            this._display(systemPermissions);
            this._display(userPermissions);

        }

        public bool hasAccess(string sectionName, string[] keys)
        {

            if (    this.systemPermissions.Count == 0
                 || this.userPermissions.Count == 0
                 || keys.Count() == 0
                 || String.IsNullOrEmpty(sectionName) 
             )
            {
                return false;
            }

            int systemReq = this._mapPermissions(sectionName, keys, systemPermissions); ;
            int userPerm = this._mapPermissions(sectionName, keys, userPermissions);



            int check =  systemReq & userPerm;

            Common.Alert("System Value" + systemReq + Environment.NewLine +"User Value " + userPerm + Environment.NewLine + "AND Results " + check);


            if (check == 0)
            {
                return false;
            }

            return true;

        }

        private int _mapPermissions(string secName, string[] keys, Dictionary<PermissionsKey, int> dic)
        {

            int newKey = 0;
            var vv = new PermissionsKey();

            foreach (string k in keys)
            {
                vv.sectionName = secName;
                vv.KeyIndex = k;

                if (dic.ContainsKey(vv))
                {
                    newKey |= dic[vv] ;
                }
            }

            return newKey;
        }



        private void _getSystemPrivleges()
        {
            var db = new dbConnetion();

            string sql =   " SELECT SQL_CACHE "
                         + " KeyIndex, Value, sectionName "
                         + " FROM role_keys "
                         + " WHERE status = 'active' ";

            foreach (var i in db.getData(sql, null, r => new SystemPermissions()
                                                           {
                                                                 pIndex = r["KeyIndex"].ToString()
                                                               , pSec = r["sectionName"].ToString()
                                                               , pValue = r["Value"].ToString()
                                                           }
                                           )
                    )
            {

                var vv = new PermissionsKey();
                vv.sectionName = i.pSec;
                vv.KeyIndex = i.pIndex;
                systemPermissions.Add(vv, Convert.ToInt32(i.pValue));

            }
        }

        private void _getUserPrivleges()
        {
            var db = new dbConnetion();

            string sql = " SELECT SQL_CACHE k.sectionName, k.KeyIndex, k.value "
                        +" FROM users AS su "
                        +" INNER JOIN role_relation AS r ON r.roleID = su.roleID "
                        +" INNER JOIN role_keys AS k ON k.KeyID = r.KeyID "
                        +" WHERE su.status = 'active' AND su.userID = @userid ";

            var parms = new List<MySqlParameter>();
            parms.Add(new MySqlParameter("@userid", UserInfo.UserID));

            foreach (var i in db.getData(sql, parms , r => new SystemPermissions()
                                                            {
                                                                  pIndex = r["KeyIndex"].ToString()
                                                                , pSec = r["sectionName"].ToString()
                                                                , pValue = r["Value"].ToString()
                                                            }
                                           )
                    )
            {
                var vv = new PermissionsKey();
                vv.sectionName = i.pSec;
                vv.KeyIndex = i.pIndex;

                userPermissions.Add(vv, Convert.ToInt32(i.pValue));
            }
        }



        private void _display(Dictionary<PermissionsKey, int> dic)
        {
            string str = "";
            foreach (KeyValuePair<PermissionsKey, int> d in dic)
            {
                var vv = new PermissionsKey();
                var c = d.Key;

                str += c.sectionName + "_" + c.KeyIndex + "  =>  " + d.Value.ToString() + Environment.NewLine;

            }

            Common.Alert(str);
        }



        private int _BinToInt(string str)
        {
            Regex binary = new Regex("^[01]{1,32}$", RegexOptions.Compiled);
            int val = -1;

            if (binary.IsMatch(str))
            {
                val = Convert.ToInt32(str, 2);
            }

            return val;
        }

        private string _IntToBin(int number)
        {
            return Convert.ToString(number, 2);
        }


    }

    class SystemPermissions{
        public string pIndex;
        public string pSec;
        public string pValue;
    }

    class PermissionsKey
    {
        public string sectionName;
        public string KeyIndex;
    }
}

And I use this class like so

    var role = new Roles();
    string[] aa = { "view", "use" };

    if (role.hasAccess("system", aa))
    {
        Common.Alert("Welcome");
    }
    else
    {
        Common.Alert("NOP!");
    }

Note that the method Common.Alert() it simply display a Message (ie. MessageBox)

The above code displays the following when it runs

1) the content of the systemPermissions dictionary in the following order PermissionsKey.sectionName_PermissionsKey.KeyIndex => the value (ie. 1 , 2, 4, 8, 16, 32, 64 .....) system_do = > 1 system_use => 2 system_view => 4 test_use => 1 test_view => 2

2) The content of the usePermissions dictionary

system_do = > 1
test_use => 1

The issue is that the 2 variables systemReq and userPerm return 0 every time. I am not sure why

 int systemReq = this._mapPermissions(sectionName, keys, systemPermissions); ;
 int userPerm = this._mapPermissions(sectionName, keys, userPermissions);
Jaylen
  • 39,043
  • 40
  • 128
  • 221
  • You may need to define gethashcode and equals on your PermissionKey type, otherwise the dictionary doesn't know when two things are equivalent. – sircodesalot Jan 03 '15 at 01:32
  • Can you please show me what you mean by gethashcode? – Jaylen Jan 03 '15 at 01:34
  • Hashing basically works by segmenting items into buckets. For example, if you have 100 items, and 10 buckets, then you only need to determine which bucket an item belongs to (by using GetHashCode) and then searching within that bucket. That brings the search down from 100 items to just searching 10 (which is why dictionaries are so fast). That said, for it to know which bucket something goes into, you need to define a hashing function. – sircodesalot Jan 03 '15 at 01:39
  • A hashing function in C#/Java works by basically retuning a number (`Int32`). What the number is, isn't important so long as you consistently get the same number back for the same 'type' (whatever that means in your implementation). So two things that are *equal* should also have the same hash code as well (so that they go into the same bucket - dictionary will only test equality on two items in the same bucket!). A simple solution for example is just to return `(KeyIndex+sectionName).getHashCode()`. It's primitive but I think it will work in your scenario. – sircodesalot Jan 03 '15 at 01:43
  • Hint: consider searching for title of your question on Google/Bing. (SO search engine sometimes does not bring the best matches) to verify if you are really the first person having trouble with this ... I suspected that some people already tried to use custom objects as key in dictionary and indeed according to this search http://www.bing.com/search?q=Dictionary.ContainsKey()+in+C%23+is+not+finding+a+key+in+the+Dictionary some people already encountered similar issue. – Alexei Levenkov Jan 03 '15 at 03:44

1 Answers1

10

In order to use something as a dictionary key and expect it to follow you expectations of what is considered equals, you actually need to define what is considered the same.

In your PermissionsKey class, you do not override Equals, which means that the default Equals method is still in play (which is the object reference, and is unique for each specific object).

To fix, you need to actually tell the dictionary how to evaluate equality. And anytime you override Equals, you also should also override GetHashCode:

class PermissionsKey
{
    private string sectionName;
    private string keyIndex;

    public string SectionName { get { return sectionName; } }
    public string KeyIndex { get { return keyIndex; } }

    public PermissionsKey(string sectionName, string keyIndex)
    {
        this.sectionName = sectionName;
        this.keyIndex = keyIndex;
    }

    public override bool Equals(object obj)
    {
        var key = obj as PermissionsKey;

        if (key == null)
            return false;

        return sectionName.Equals(key.sectionName) &&
               keyIndex.Equals(key.keyIndex);
    }


    //Credit to Jon Skeet from https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
    //  for inspiration for this method
    public override int GetHashCode()
    {
        unchecked // Overflow is fine, just wrap
        {
            int hash = (int) 2166136261;
            // Suitable nullity checks etc, of course :)
            hash = hash * 16777619 ^ sectionName.GetHashCode();
            hash = hash * 16777619 ^ keyIndex.GetHashCode();
            return hash;
        }
    }
}

You are overriding Equals to actually define the equality comparison. Anytime you try to compare 2 objects (specifically in the Dictionary object, but any other time you do it by calling .Equals), you should define your own comparison. If you don't, then you are just using the default equality comparison, which is a pure objection comparison and 2 objects unless created from the same exact object will never be equals.

Similarly, when you override Equals, you are strongly advised to also override GetHashCode. The HashCode defines which "bucket" the object falls into. A good hashcode that follows the Equals implementation will help speed up comparisons by placing only objects with the same hash code into the same buckets. The standard recommendation is 2 objects that are Equals will always have the same hashcode, but it is possible for 2 objects that are not equals to have the same hashcode. Effectively it is a quick way for the framework to check for inequality. If you get 2 different hashcodes, the framework knows the objects aren't equal, but if the hashcodes are the same, then it goes and checks equality.

If you can't or don't want to override Equals and GetHashCode, then you also have the option of definining an IEqualityComparer<T> in the constructor of the dictionary,

You just have to pass an object that implements IEqualityComparer<T> and within that object you can define the equality comparisons you want.

In addition, anything you plan to use as a dictionary key should never be mutable, specifically the fields that are used to calculate equality and calculate the hashcode, otherwise your keys can get lost as the hash code for the object does not match what was originally put into the dictionary. I have modified your original example to change your fields in PermissionsKey to immutable properties and added a constructor to allow you to set them once when initially created.

Community
  • 1
  • 1
psubsee2003
  • 8,563
  • 8
  • 61
  • 79
  • Yup, this right here. – sircodesalot Jan 03 '15 at 01:40
  • I am not sure how to implement the Equals or the `GetHashCode`. I never use those so I am not sure what they should so. what would the logic behind the Equals need to look like. basically the combination of `sectionName` and `KeyIndex` are a key in the dictionary – Jaylen Jan 03 '15 at 01:58
  • @Mike for getashcode, looks at the post I linked in the answer for some details, but I'll give you an idea on both. – psubsee2003 Jan 03 '15 at 02:05
  • Thank you. Can you please help me understand what is the job of `GetHashCode()` what is this function there for? – Jaylen Jan 03 '15 at 02:07
  • 1
    @Mike not trying to be harsh, but you need to learn to google.... just entering "GetHashCode" into a google search pointed me to http://stackoverflow.com/questions/371328/why-is-it-important-to-override-gethashcode-when-equals-method-is-overridden as well as http://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx which is the official documentation on the subject. – psubsee2003 Jan 03 '15 at 02:10
  • Side note: @psubsee2003 since you wrote complete class you should have made it immutable. Looking at someone trying to debug code that used mutable keys is very entertaining but really unfair to do if you know what will happen. – Alexei Levenkov Jan 03 '15 at 03:50
  • @AlexeiLevenkov excellent point – psubsee2003 Jan 03 '15 at 07:54
  • @Mike please take note of my edit (changes to the code and a new last paragraph). Alexei Levenkov made an excellent observation that I had overlooked when I explained Equals and GetHashCode. – psubsee2003 Jan 03 '15 at 08:06
  • @psubsee2003 believe me I am not lazy or ignorant. All it is that I am new to c#. In fact, this is my first time using it c# or windows applications. I Googled and viewed the URL you shared prior commenting. I seen the code and I could have copied it, but I did not understand it so I chose to ask so I can learn. I was hoping you could have explained it easier for me (for a rookie.) I have used your code even though, I still do not understand it. May be I am not exactly sure what it `getHashcode()` function should do. Thank you so much for your help – Jaylen Jan 03 '15 at 16:58
  • 1
    @Mike the concept of a Hash is not unique to c#. You will see something similar in most object oriented languages. The easiest explanation is the Hash Code is a non-unique id. When you want to say if `x` and `y` are equals then they **must** have the same Hash Code. But just because 2 objects have the same Hash Code doesn't mean that they are equals. – psubsee2003 Jan 03 '15 at 17:26