Supercharge Your C# Skills: Dynamic Field Resetting with Reflection

Pauline Pun
Mighty Bear Games
Published in
6 min readJun 12, 2024

Ever found yourself in a coding maze, juggling object states and wishing for a magic wand to reset things on the fly? Here we explore a powerful technique that aids in achieving this — dynamic field resetting with reflection.

From unit testing to data initialization in-game, we will explore the complexities of dynamic field resetting and demonstrate its practical applications.

Photo by Alexander Sinn on Unsplash

In this article, we’ll learn how to build a simple unit test case in Unity with C#, for testing object resetting. We’ll explore how to retrieve fields, analyze their types and attributes, reset their values, and assert the correctness of the reset behavior. Additionally, we’ll discuss potential use cases and the significance of ensuring robust reset functionality in C# projects.

Before diving into the code, let’s understand the problem we’re trying to solve. Suppose we have a C# class with numerous fields and we want to reset these fields to their default or random values programmatically. Reflection is a powerful feature in C# that allows you to inspect and manipulate types, members, and objects dynamically at runtime. In this example, reflection is used primarily for two purposes:

Field Retrieval:

  • By using GetType().GetFields(), the code retrieves all fields of the specified object type (IResetBehaviour). This allows the method to access fields regardless of their access modifiers (public, private, etc.) or whether they are declared directly in the class or inherited from a base class.

Field Modification:

  • Reflection enables the code to dynamically set values to fields using the SetValue() method. This allows the method to modify the state of object fields without knowing their names or types at compile time. Additionally, reflection is used to get and set property values of ReactiveProperty type and other complex types, allowing for more flexible manipulation.

Now let’s dive into the code and see how we can implement the unit test.

Interface class for all ResetBehaviour game object.

The above interface will allow us to identify the behavior which will be used for unit testing. As the function implies we want any behavior inheriting this to include all necessary variables to be reset when ResetFields() is called.

Unit Test framework.

In CheckNetworkBehaviourFields_HaveToBeReset, we gather all GameObjects implementing IResetBehaviour and iterate through the required fields, which should be reset, then check for validity and correctness of the reset states.

CheckResettableFields will filter out the necessary fields we specify to check while CheckResetField does the actual data validation. We will also look at supporting our own user-defined class types in GetUserDefinedInstance.

Fields filtering and logging.

Let’s take a look at CheckResettableFields implementation.

  1. We start by obtaining information about the fields present in the IResetBehaviour object. Using reflection, we retrieve all fields regardless of their visibility (public or non-public).
  2. We iterate through each field and check if it should be ignored based on predefined criteria. In this case, I’d like to ignore certain fields from the ignoreFieldNames list or objects of type IDisposable.
  3. Next, we decide which fields need to be tested. These are typically fields of certain fundamental types like boolean, numeric types, strings, date-time, vectors, or interfaces. For debugging purposes, we will also construct a log containing information about each field.
  4. We examine any custom attributes associated with the field. If a field has certain attributes, such as SerializeFieldAttribute or IgnoreResetAttribute, we mark it as not required for testing.
  5. For each field, we call field.SetValue() to set a random value appropriate to its type. For example, for strings, we set a predefined random string; for numeric types, we set a random integer value; and so on. If the field is a reference type (class or interface), we call GetUserDefinedInstance to obtain an instance for testing.
  6. After setting the values, we trigger the reset behavior by calling ResetFields(). Then, we retrieve the field's value again to check if it has been reset properly.
  7. Finally, we call CheckResetField to verify if the field has been reset correctly by comparing its original value (originalVal) with the value before reset (newVal) and after reset (resetVal).

This method essentially automates the testing process for resetting behavior of fields within an object, ensuring that they revert to their initial state as expected.

Bonus code on how to set up custom attribute:

User-defined attribute.
Handling user-defined types.

There are cases where just handling system types is insufficient for our test cases, that’s where GetUserDefinedInstance comes in handy. By determining the field type, we can ensure that the testing framework can dynamically create instances of user-defined classes as required for testing.

Checking values.

Finally in CheckResetField we check the the field has been modified previously when we call field.SetValue() from its original value. Following that, we validate that ResetFields() has indeed reset the field to the original value as intended.

When everything is placed together we’ll get the following:

Final_final.cs
Photo by Goran Ivos on Unsplash

The provided code demonstrates a versatile approach to dynamically resetting object fields in C# using reflection and various strategies based on field types. Here are some ways this concept can be further used or extended:

  1. Serialization and Deserialization:

Integrate this dynamic resetting logic with serialization/deserialization processes. For example, you could reset fields before serialization and restore them after deserialization to ensure consistent object states.

2. Unit Testing:

Leverage this code in unit testing scenarios. You can use it to set up initial object states, perform tests, and then reset the object states for subsequent tests. This can lead to more robust and isolated unit tests.

3. Data Initialization:

Extend the dynamic resetting logic for initializing data in your application. It can be beneficial in scenarios where you need to reset or randomize specific fields for testing or simulation purposes.

4. Object State Management:

Apply this approach to manage and maintain object states in applications where dynamic field changes are required. For instance, in game development, you might use this logic to reset the state of game objects or entities.

5. Automated Test Data Generation:

Use the code for generating automated test data. If you have specific requirements for test scenarios, this approach can help create diverse sets of test data by dynamically resetting fields.

6. Dynamic UI Initialization:

Apply the dynamic resetting logic in user interface (UI) development to initialize and reset UI elements dynamically. This can be useful for creating dynamic and responsive UI components.

7. Object Prototyping:

Utilize the code for prototyping and rapid development. It allows you to quickly set up and reset object states during the development phase, making it easier to experiment with different configurations.

8. Integration with Dependency Injection:

Integrate this logic with dependency injection frameworks. It can be helpful when managing the initialization and resetting of injected dependencies within your application.

9. Custom Attribute Extensions:

Extend the code by introducing custom attributes to mark fields with specific behaviors. This can enhance the flexibility and configurability of the dynamic resetting process.

10. Versioning and Migration:

Use this code in versioning and migration scenarios. When object structures evolve, dynamic resetting can be applied to handle changes in fields, ensuring backward or forward compatibility.

Dynamic field resetting in C# provides a flexible solution for managing object states programmatically. By leveraging reflection and conditional logic, developers can efficiently reset fields based on various criteria, enhancing code maintainability and testability. However, it should be used judiciously due to its performance implications and potential complexity.

👏 Thank you for reading! If you found this article helpful, don’t forget to give it some claps to show your appreciation.

👉 Follow Mighty Bear Games on Medium for more insightful articles, tips, and tutorials on game development. Stay tuned for our upcoming content!

Happy coding! 🚀

--

--