1. Hi All,

    Since this took me longer to achieve than I'm comfortable admitting.. Here is a quick guide on how to use reflections to access private fields in C#

    Note from Ryan: Reflection is a very power hungry method. You should try to avoid using it as it can add some noticeable latency to a server.
    Experienced developers will beat you with a stick if they see a reflection in your coding.
    Using a Reflection in the way I have outlined in the example is extremely undesirable. If you feel you cannot achieve what you are trying to do without calling a reflection, think of a way you can heavily limit the amount of times the reflection is called.

    Please also note, the terminology may not be 100% correct, but this a noobs guide and I have written it in a way to aid in the explanation of the process without the confusion of unknown terminology.

    In this case I'll be accessing a private field, namely the lifeStory field located within the BasePlayer class, which is an Object of the PlayerLifeStory class.

    Code:
    PlayerLifeStory lifeStory = typeof(BasePlayer).GetField("lifeStory", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(player) as PlayerLifeStory;
    Above is the end result to access the private field "lifeStory" within the BasePlayer class.

    To break this down into digestible chunks is;

    1) Declare an object of the class you are retrieving(in this case, PlayerLifeStory)
    Code:
    PlayerLifeStory lifeStory = ...
    2) Obtain the typeof BasePlayer as the GetField method resides within this
    Code:
    PlayerLifeStory lifeStory = typeof(BasePlayer)....
    3) Utilize the GetField method with the parameters "lifeStory"(being the private field you want to access) and the BindingFlags: BindingFlags.NonPublic & BindingFlags.Instance(You may have to utilize BindingFlags.Static or BindingFlags.Public accordingly also)
    Code:
    PlayerLifeStory lifeStory = typeof(BasePlayer).GetField("lifeStory", BindingFlags.Instance | BindingFlags.NonPublic)...
    4) Utilize the GetValue method which will then associate the Field with an Object(player) of the Type you retrieved in step 2(BasePlayer in this case)
    Code:
    PlayerLifeStory lifeStory = typeof(BasePlayer).GetField("lifeStory", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(player)
    5) Cast the result of GetValue to the Class of the of the private field you are trying to access
    Code:
    PlayerLifeStory lifeStory = typeof(BasePlayer).GetField("lifeStory", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(player) as PlayerLifeStory
    6) Rejoice in your success! You can now utilize the Private Field you were trying to access as if it was a public field. For example:
    Code:
    private object OnPlayerLand(BasePlayer player)
    {
       PlayerLifeStory lifeStory = typeof(BasePlayer).GetField("lifeStory", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(player) as PlayerLifeStory;
       if(lifeStory.secondsAlive< 3)
       {
          Puts("Player has been alive for more than 3 seconds");
       }
    }
    
    Now that you know how to access private fields, reference this post and you will be able to work backwards to access private methods or set values of fields etc.
     
    Last edited by a moderator: Dec 20, 2017
  2. You lost me at Hi All
     
  3. haha, For the most part, you will be able to copy step 6's example into a new plugin template and play with it until you understand.
     
  4. Please bare in mind reflection is a very power hungry method. So doing it as frequently as you do is strongly unreccomended from me.

    That hook is an extremely frequent hook to be called and using reflection everytime it’s called will most likely add some noticeable latency to the server.
     
  5. Definitely agree, I'll edit the post to make this point apparent. I use it after a whole heap of conditional checking and not in that exact way.
     
  6. In this specific case, reflection isn't needed (though I realize the point of this thread is more than that). BasePlayer.TimeAlive() should return lifestory.secondsAlive.
     
  7. Wulf

    Wulf Community Admin

    If you do have something you think should be public, just let us know and we will often expose it.
     
  8. 100%, Just an example as you stated.

    I'd love to see the lifeStory become public. It's a very useful object for statistics tracking. Where should I be posted for requests of this nature?
     
  9. I think the term "very power hungry" is not 100% accurate. Sure, it's a bit heavier than you'd like, but it isn't a super large hit.

    I did a benchmark of getting the time alive through lifestory reflection vs BasePlayer.TimeAlive(), ran a loop 1000 times. The reflection one took 0.1428ms, the non-reflection took 0.0215ms.

    For what it's worth, I declared the FieldInfo outside of the loop, and was just calling .GetValue each time. I'm sure there is some difference there, but I think it's bad practice to be doing GetField every time you want to access it anyway.
     
  10. Out of curiosity, could you repeat the benchmark with the FieldInfo inside?
     
  11. Quite a bit higher. 2ms compared to 0.1ms.

    Snippet I used for testing (lifeStory fieldinfo not included as it's outside):
    Code:
    var rngPlayer = BasePlayer.activePlayerList.GetRandom();
                if (rngPlayer != null)
                {
                    var watch = new Stopwatch();
                    watch.Start();
                    for(int i = 0; i < 1000; i++)
                    {
                        var life = lifeStory.GetValue(rngPlayer);
                        var timeAlive = (life as ProtoBuf.PlayerLifeStory)?.secondsAlive;
                    }
                    watch.Stop();
                    PrintWarning("lifeStory.GetValue x1000 took: " + watch.Elapsed.TotalMilliseconds + "ms");
                    watch.Reset();
                    watch.Start();
                    for (int i = 0; i < 1000; i++) { var timeAlive = rngPlayer.TimeAlive(); }
                    watch.Stop();
                    PrintWarning("BasePlayer.TimeAlive() x1000 took: " + watch.Elapsed.TotalMilliseconds + "ms");
                    watch.Reset();
                    watch.Start();
                    for (int i = 0; i < 1000; i++)
                    {
                        var lifeField = typeof(BasePlayer).GetField("lifeStory", (BindingFlags.Instance | BindingFlags.NonPublic));
                        var life = lifeField.GetValue(rngPlayer);
                        var timeAlive = (life as ProtoBuf.PlayerLifeStory)?.secondsAlive;
                    }
                    watch.Stop();
                    PrintWarning("lifeField x1000 took: " + watch.Elapsed.TotalMilliseconds + "ms");
                }
    
     
  12. Cheers! I assumed the field info was going to be the culprit to the higher latency.
     
  13. Reflection is a means to an end in the case of plugin development as you are working with a codebase that isn't yours and you sometimes want or need to use the values of private fields/properties or invoke private methods. It all depends on how, where and when you use it.

    For those interested I can suggest having a look at this: Why is reflection slow?
     
  14. If you use private members -> create feature request post Feature Suggestions | Oxide
    Example: I need public access to private field BasePlayer.???
    And Wulf will add this